package ij;
import ij.gui.*;
import ij.process.*;
import ij.text.*;
import ij.io.*;
import ij.plugin.*;
import ij.plugin.filter.*;
import ij.util.Tools;
import ij.plugin.frame.Recorder;
import ij.plugin.frame.ThresholdAdjuster;
import ij.macro.Interpreter;
import ij.macro.MacroRunner;
import ij.measure.Calibration;
import ij.measure.ResultsTable;
import ij.measure.Measurements;
import java.awt.event.*;
import java.text.*;
import java.util.*; 
import java.awt.*;  
import java.applet.Applet;
import java.io.*;
import java.lang.reflect.*;
import java.net.*;
import javax.net.ssl.*;
import java.security.cert.*;
import java.security.KeyStore;
import java.nio.ByteBuffer;
import java.math.RoundingMode;
public class IJ {
    
    public static Font font10 = new Font("SansSerif", Font.PLAIN, 10);
    
    public static Font font12 = ImageJ.SansSerif12;
    
    public static Font font14 = ImageJ.SansSerif14;
    
    
    public static final int COMPOSITE=1, COLOR=2, GRAYSCALE=3;
    
    public static final String URL = "http://imagej.nih.gov/ij";
    public static final int ALL_KEYS = -1;
    
    
    public static boolean debugMode;
    
    public static boolean hideProcessStackDialog;
        
    public static final char micronSymbol = '\u00B5';
    public static final char angstromSymbol = '\u00C5';
    public static final char degreeSymbol = '\u00B0';
    private static ImageJ ij;
    private static java.applet.Applet applet;
    private static ProgressBar progressBar;
    private static TextPanel textPanel;
    private static String osname, osarch;
    private static boolean isMac, isWin, isLinux, is64Bit;
    private static int javaVersion;
    private static boolean controlDown, altDown, spaceDown, shiftDown;
    private static boolean macroRunning;
    private static Thread previousThread;
    private static TextPanel logPanel;
    private static ClassLoader classLoader;
    private static boolean memMessageDisplayed;
    private static long maxMemory;
    private static boolean escapePressed;
    private static boolean redirectErrorMessages;
    private static boolean suppressPluginNotFoundError;
    private static Hashtable commandTable;
    private static Vector eventListeners = new Vector();
    private static String lastErrorMessage;
    private static Properties properties;   private static DecimalFormat[] df;
    private static DecimalFormat[] sf;
    private static DecimalFormatSymbols dfs;
    private static boolean trustManagerCreated;
    private static String smoothMacro;
    private static Interpreter macroInterpreter;
    private static boolean protectStatusBar;
    private static Thread statusBarThread;
            
    static {
        osname = System.getProperty("os.name");
        isWin = osname.startsWith("Windows");
        isMac = !isWin && osname.startsWith("Mac");
        isLinux = osname.startsWith("Linux");
        String version = System.getProperty("java.version");
        if (version==null || version.length()<2)
            version = "1.8";
        if (version.startsWith("1.8"))
            javaVersion = 8;
        else if (version.charAt(0)=='1' && Character.isDigit(version.charAt(1)))
            javaVersion = 10 + (version.charAt(1) - '0');
        else if (version.charAt(0)=='2' && Character.isDigit(version.charAt(1)))
            javaVersion = 20 + (version.charAt(1) - '0');
        else if (version.startsWith("1.6"))
            javaVersion = 6;
        else if (version.startsWith("1.9")||version.startsWith("9"))
            javaVersion = 9;
        else if (version.startsWith("1.7"))
            javaVersion = 7;
        else
            javaVersion = 8;
        dfs = new DecimalFormatSymbols(Locale.US);
        df = new DecimalFormat[10];
        df[0] = new DecimalFormat("0", dfs);
        df[1] = new DecimalFormat("0.0", dfs);
        df[2] = new DecimalFormat("0.00", dfs);
        df[3] = new DecimalFormat("0.000", dfs);
        df[4] = new DecimalFormat("0.0000", dfs);
        df[5] = new DecimalFormat("0.00000", dfs);
        df[6] = new DecimalFormat("0.000000", dfs);
        df[7] = new DecimalFormat("0.0000000", dfs);
        df[8] = new DecimalFormat("0.00000000", dfs);
        df[9] = new DecimalFormat("0.000000000", dfs);
        df[0].setRoundingMode(RoundingMode.HALF_UP);
    }
            
    static void init(ImageJ imagej, Applet theApplet) {
        ij = imagej;
        applet = theApplet;
        progressBar = ij.getProgressBar();
    }
    static void cleanup() {
        ij=null; applet=null; progressBar=null; textPanel=null;
    }
    
    public static ImageJ getInstance() {
        return ij;
    }
    
    
    public static void setDebugMode(boolean b) {
        debugMode = b;
        LogStream.redirectSystem(debugMode);
    }
    
    public static String runMacro(String macro) {
        return runMacro(macro, "");
    }
    
    public static String runMacro(String macro, String arg) {
        Macro_Runner mr = new Macro_Runner();
        return mr.runMacro(macro, arg);
    }
    
    public static String runMacroFile(String name, String arg) {
        Macro_Runner mr = new Macro_Runner();
        return mr.runMacroFile(name, arg);
    }
    
    public static String runMacroFile(String name) {
        return runMacroFile(name, null);
    }
    
    public static Object runPlugIn(ImagePlus imp, String className, String arg) {
        if (imp!=null) {
            ImagePlus temp = WindowManager.getTempCurrentImage();
            WindowManager.setTempCurrentImage(imp);
            Object o = runPlugIn("", className, arg);
            WindowManager.setTempCurrentImage(temp);
            return o;
        } else
            return runPlugIn(className, arg);
    }
    
    public static Object runPlugIn(String className, String arg) {
        return runPlugIn("", className, arg);
    }
    
    
    public static Object runPlugIn(String commandName, String className, String arg) {
        if (arg==null) arg = "";
        if (IJ.debugMode)
            IJ.log("runPlugIn: "+className+argument(arg));
                        if (!className.startsWith("ij.") && applet==null)
            return runUserPlugIn(commandName, className, arg, false);
        Object thePlugIn=null;
        try {
            Class c = Class.forName(className);
            thePlugIn = c.newInstance();
            if (thePlugIn instanceof PlugIn)
                ((PlugIn)thePlugIn).run(arg);
            else
                new PlugInFilterRunner(thePlugIn, commandName, arg);
        } catch (ClassNotFoundException e) {
            if (!(className!=null && className.startsWith("ij.plugin.MacAdapter"))) {
                log("Plugin or class not found: \"" + className + "\"\n(" + e+")");
                String path = Prefs.getCustomPropsPath();
                if (path!=null);
                    log("Error may be due to custom properties at " + path);
            }
        }
        catch (InstantiationException e) {log("Unable to load plugin (ins)");}
        catch (IllegalAccessException e) {log("Unable to load plugin, possibly \nbecause it is not public.");}
        redirectErrorMessages = false;
        return thePlugIn;
    }
        
    static Object runUserPlugIn(String commandName, String className, String arg, boolean createNewLoader) {
        if (IJ.debugMode)
            IJ.log("runUserPlugIn: "+className+", arg="+argument(arg));
        if (applet!=null) return null;
        if (createNewLoader)
            classLoader = null;
        ClassLoader loader = getClassLoader();
        Object thePlugIn = null;
        try { 
            thePlugIn = (loader.loadClass(className)).newInstance(); 
            if (thePlugIn instanceof PlugIn)
                ((PlugIn)thePlugIn).run(arg);
            else if (thePlugIn instanceof PlugInFilter)
                new PlugInFilterRunner(thePlugIn, commandName, arg);
        }
        catch (ClassNotFoundException e) {
            if (className.startsWith("macro:"))
                runMacro(className.substring(6));
            else if (className.contains("_")  && !suppressPluginNotFoundError)
                error("Plugin or class not found: \"" + className + "\"\n(" + e+")");
        }
        catch (NoClassDefFoundError e) {
            int dotIndex = className.indexOf('.');
            if (dotIndex>=0 && className.contains("_")) {
                                if (debugMode) IJ.log("runUserPlugIn: rerunning "+className);
                return runUserPlugIn(commandName, className.substring(dotIndex+1), arg, createNewLoader);
            }
            if (className.contains("_") && !suppressPluginNotFoundError)
                error("Run User Plugin", "Class not found while attempting to run \"" + className + "\"\n \n   " + e);
        }
        catch (InstantiationException e) {error("Unable to load plugin (ins)");}
        catch (IllegalAccessException e) {error("Unable to load plugin, possibly \nbecause it is not public.");}
        if (thePlugIn!=null && !"HandleExtraFileTypes".equals(className))
            redirectErrorMessages = false;
        suppressPluginNotFoundError = false;
        return thePlugIn;
    } 
    
    private static String argument(String arg) {
        return arg!=null && !arg.equals("") && !arg.contains("\n")?"(\""+arg+"\")":"";
    }
    static void wrongType(int capabilities, String cmd) {
        String s = "\""+cmd+"\" requires an image of type:\n \n";
        if ((capabilities&PlugInFilter.DOES_8G)!=0) s +=  "    8-bit grayscale\n";
        if ((capabilities&PlugInFilter.DOES_8C)!=0) s +=  "    8-bit color\n";
        if ((capabilities&PlugInFilter.DOES_16)!=0) s +=  "    16-bit grayscale\n";
        if ((capabilities&PlugInFilter.DOES_32)!=0) s +=  "    32-bit (float) grayscale\n";
        if ((capabilities&PlugInFilter.DOES_RGB)!=0) s += "    RGB color\n";
        error(s);
    }
    
    
    public static void doCommand(String command) {
        new Executer(command, null);
    }
    
    
    public static void doCommand(ImagePlus imp, String command) {
        new Executer(command, imp);
    }
    
    
    public static void run(String command) {
        run(command, null);
    }
    
    
    public static void run(String command, String options) {
                if (ij==null && Menus.getCommands()==null)
            init();
        Macro.abort = false;
        Macro.setOptions(options);
        Thread thread = Thread.currentThread();
        if (previousThread==null || thread!=previousThread) {
            String name = thread.getName();
            if (!name.startsWith("Run$_"))
                thread.setName("Run$_"+name);
        }
        command = convert(command);
        previousThread = thread;
        macroRunning = true;
        Executer e = new Executer(command);
        e.run();
        macroRunning = false;
        Macro.setOptions(null);
        testAbort();
        macroInterpreter = null;
            }
    
    
    public static void run(Interpreter interpreter, String command, String options) {
        macroInterpreter = interpreter;
        run(command, options);
        macroInterpreter = null;
    }
    
    private static String convert(String command) {
        if (commandTable==null) {
            commandTable = new Hashtable(30);
            commandTable.put("New...", "Image...");
            commandTable.put("Threshold", "Make Binary");
            commandTable.put("Display...", "Appearance...");
            commandTable.put("Start Animation", "Start Animation [\\]");
            commandTable.put("Convert Images to Stack", "Images to Stack");
            commandTable.put("Convert Stack to Images", "Stack to Images");
            commandTable.put("Convert Stack to RGB", "Stack to RGB");
            commandTable.put("Convert to Composite", "Make Composite");
            commandTable.put("RGB Split", "Split Channels");
            commandTable.put("RGB Merge...", "Merge Channels...");
            commandTable.put("Channels...", "Channels Tool...");
            commandTable.put("New... ", "Table...");
            commandTable.put("Arbitrarily...", "Rotate... ");
            commandTable.put("Measurements...", "Results... ");
            commandTable.put("List Commands...", "Find Commands...");
            commandTable.put("Capture Screen ", "Capture Screen");
            commandTable.put("Add to Manager ", "Add to Manager");
            commandTable.put("In", "In [+]");
            commandTable.put("Out", "Out [-]");
            commandTable.put("Enhance Contrast", "Enhance Contrast...");
            commandTable.put("XY Coodinates... ", "XY Coordinates... ");
            commandTable.put("Statistics...", "Statistics");
            commandTable.put("Channels Tool... ", "Channels Tool...");
            commandTable.put("Profile Plot Options...", "Plots...");
            commandTable.put("AuPbSn 40 (56K)", "AuPbSn 40");
            commandTable.put("Bat Cochlea Volume (19K)", "Bat Cochlea Volume");
            commandTable.put("Bat Cochlea Renderings (449K)", "Bat Cochlea Renderings");
            commandTable.put("Blobs (25K)", "Blobs");
            commandTable.put("Boats (356K)", "Boats");          
            commandTable.put("Cardio (768K, RGB DICOM)", "Cardio (RGB DICOM)");
            commandTable.put("Cell Colony (31K)", "Cell Colony");
            commandTable.put("Clown (14K)", "Clown");
            commandTable.put("Confocal Series (2.2MB)", "Confocal Series");
            commandTable.put("CT (420K, 16-bit DICOM)", "CT (16-bit DICOM)");           
            commandTable.put("Dot Blot (7K)", "Dot Blot");
            commandTable.put("Embryos (42K)", "Embryos");
            commandTable.put("Fluorescent Cells (400K)", "Fluorescent Cells");
            commandTable.put("Fly Brain (1MB)", "Fly Brain");           
            commandTable.put("Gel (105K)", "Gel");
            commandTable.put("HeLa Cells (1.3M, 48-bit RGB)", "HeLa Cells (48-bit RGB)");
            commandTable.put("Leaf (36K)", "Leaf");
            commandTable.put("Line Graph (21K)", "Line Graph");         
            commandTable.put("Mitosis (26MB, 5D stack)", "Mitosis (5D stack)");
            commandTable.put("MRI Stack (528K)", "MRI Stack");
            commandTable.put("M51 Galaxy (177K, 16-bits)", "M51 Galaxy (16-bits)");
            commandTable.put("Neuron (1.6M, 5 channels)", "Neuron (5 channels)");
            commandTable.put("Nile Bend (1.9M)", "Nile Bend");          
            commandTable.put("Organ of Corti (2.8M, 4D stack)", "Organ of Corti (4D stack)");
            commandTable.put("Particles (75K)", "Particles");
            commandTable.put("T1 Head (2.4M, 16-bits)", "T1 Head (16-bits)");
            commandTable.put("T1 Head Renderings (736K)", "T1 Head Renderings");
            commandTable.put("TEM Filter (112K)", "TEM Filter");
            commandTable.put("Tree Rings (48K)", "Tree Rings");
        }
        String command2 = (String)commandTable.get(command);
        if (command2!=null)
            return command2;
        else
            return command;
    }
    
    public static void run(ImagePlus imp, String command, String options) {
        if (ij==null && Menus.getCommands()==null)
            init();
        if (imp!=null) {
            ImagePlus temp = WindowManager.getTempCurrentImage();
            WindowManager.setTempCurrentImage(imp);
            run(command, options);
            WindowManager.setTempCurrentImage(temp);
        } else
            run(command, options);
    }
    static void init() {
        Menus m = new Menus(null, null);
        Prefs.load(m, null);
        m.addMenuBar();
    }
    private static void testAbort() {
        if (Macro.abort)
            abort();
    }
    
    public static boolean macroRunning() {
        return macroRunning;
    }
    
    public static boolean isMacro() {
        return macroRunning || Interpreter.getInstance()!=null;
    }
    
    public static java.applet.Applet getApplet() {
        return applet;
    }
    
    
    public static void showStatus(String s) {
        if ((Interpreter.getInstance()==null&&statusBarThread==null)
        || (statusBarThread!=null&&Thread.currentThread()!=statusBarThread))
            protectStatusBar(false);
        boolean doProtect = s.startsWith("!");         if (doProtect) {
            protectStatusBar(true);
            statusBarThread = Thread.currentThread();
            s = s.substring(1);
        }
        if (doProtect || !protectStatusBar) {
            if (ij!=null)
                ij.showStatus(s);
            ImagePlus imp = WindowManager.getCurrentImage();
            ImageCanvas ic = imp!=null?imp.getCanvas():null;
            if (ic!=null)
                ic.setShowCursorStatus(s.length()==0?true:false);
        }
    }
    
     
    public static void showStatus(String message, String options) {
        showStatus(message);
        if (options==null)
            return;
        options = options.replace("flash", "");
        options = options.replace("ms", "");
        Color optionalColor = null;
        int index1 = options.indexOf("#");
        if (index1>=0) {              int index2 = options.indexOf(" ", index1);
            if (index2==-1) index2 = options.length();
            String hexColor = options.substring(index1, index2);
            optionalColor = Colors.decode(hexColor, null);
            options = options.replace(hexColor, "");
        }
        if (optionalColor==null) {              for (String c : Colors.colors) {
                if (options.contains(c)) {
                    optionalColor = Colors.getColor(c, ImageJ.backgroundColor);
                    options = options.replace(c, "");
                    break;
                }
            }
        }
        boolean flashImage = options.contains("image");
        Color defaultColor = new Color(255,255,245);
        int defaultDelay = 500;
        ImagePlus imp = WindowManager.getCurrentImage();
        if (flashImage) {
            options = options.replace("image", "");
            if (imp!=null && imp.getWindow()!=null) {
                defaultColor = Color.black;
                defaultDelay = 100;
            }
            else
                flashImage = false;
        }
        Color color = optionalColor!=null?optionalColor:defaultColor;
        int delay = (int)Tools.parseDouble(options, defaultDelay);
        if (delay>8000)
            delay = 8000;
        String colorString = null;
        ImageJ ij = IJ.getInstance();
        if (flashImage) {
            Color previousColor = imp.getWindow().getBackground();
            imp.getWindow().setBackground(color);
            if (delay>0) {
                wait(delay);            
                imp.getWindow().setBackground(previousColor);
            }
        } else if (ij!=null) {
            ij.getStatusBar().setBackground(color);
            wait(delay);
            ij.getStatusBar().setBackground(ij.backgroundColor);
        }
    }
    
    
    public static void write(String s) {
        if (textPanel==null && ij!=null)
            showResults();
        if (textPanel!=null)
                textPanel.append(s);
        else
            System.out.println(s);
    }
    private static void showResults() {
        TextWindow resultsWindow = new TextWindow("Results", "", 400, 250);
        textPanel = resultsWindow.getTextPanel();
        textPanel.setResultsTable(Analyzer.getResultsTable());
    }
    public static synchronized void log(String s) {
        if (s==null) return;
        if (logPanel==null && ij!=null) {
            TextWindow logWindow = new TextWindow("Log", "", 400, 250);
            logPanel = logWindow.getTextPanel();
            logPanel.setFont(new Font("SansSerif", Font.PLAIN, 16));
        }
        if (logPanel!=null) {
            if (s.startsWith("\\"))
                handleLogCommand(s);
            else {
                if (s.endsWith("\n")) {
                    if (s.equals("\n\n"))
                        s= "\n \n ";
                    else if (s.endsWith("\n\n"))
                        s = s.substring(0, s.length()-2)+"\n \n ";
                    else
                        s = s+" ";
                }
                logPanel.append(s);
            }
        } else {
            LogStream.redirectSystem(false);
            System.out.println(s);
        }
    }
    static void handleLogCommand(String s) {
        if (s.equals("\\Closed"))
            logPanel = null;
        else if (s.startsWith("\\Update:")) {
            int n = logPanel.getLineCount();
            String s2 = s.substring(8, s.length());
            if (n==0)
                logPanel.append(s2);
            else
                logPanel.setLine(n-1, s2);
        } else if (s.startsWith("\\Update")) {
            int cindex = s.indexOf(":");
            if (cindex==-1)
                {logPanel.append(s); return;}
            String nstr = s.substring(7, cindex);
            int line = (int)Tools.parseDouble(nstr, -1);
            if (line<0 || line>25)
                {logPanel.append(s); return;}
            int count = logPanel.getLineCount();
            while (line>=count) {
                log("");
                count++;
            }
            String s2 = s.substring(cindex+1, s.length());
            logPanel.setLine(line, s2);
        } else if (s.equals("\\Clear")) {
            logPanel.clear();
        } else if (s.startsWith("\\Heading:")) {
            logPanel.updateColumnHeadings(s.substring(10));
        } else if (s.equals("\\Close")) {
            Frame f = WindowManager.getFrame("Log");
            if (f!=null && (f instanceof TextWindow))
                ((TextWindow)f).close();
        } else
            logPanel.append(s);
    }
    
    
    public static synchronized String getLog() { 
        if (logPanel==null || ij==null)
            return null;
        else
            return logPanel.getText(); 
    } 
    public static void setColumnHeadings(String headings) {
        if (textPanel==null && ij!=null)
            showResults();
        if (textPanel!=null)
            textPanel.setColumnHeadings(headings);
        else
            System.out.println(headings);
    }
    
    public static boolean isResultsWindow() {
        return textPanel!=null;
    }
    
    
    public static void renameResults(String title) {
        Frame frame = WindowManager.getFrontWindow();
        if (frame!=null && (frame instanceof TextWindow)) {
            TextWindow tw = (TextWindow)frame;
            if (tw.getResultsTable()==null) {
                IJ.error("Rename", "\""+tw.getTitle()+"\" is not a results table");
                return;
            }
            tw.rename(title);
        } else if (isResultsWindow()) {
            TextPanel tp = getTextPanel();
            TextWindow tw = (TextWindow)tp.getParent();
            tw.rename(title);
        }
    }
    
    public static void renameResults(String oldTitle, String newTitle) {
        Frame frame = WindowManager.getFrame(oldTitle);
        if (frame==null) {
            error("Rename", "\""+oldTitle+"\" not found");
            return;
        } else if (frame instanceof TextWindow) {
            TextWindow tw = (TextWindow)frame;
            if (tw.getResultsTable()==null) {
                error("Rename", "\""+oldTitle+"\" is not a table");
                return;
            }
            tw.rename(newTitle);
        } else
            error("Rename", "\""+oldTitle+"\" is not a table");
    }
    
    public static void deleteRows(int row1, int row2) {
        ResultsTable rt = Analyzer.getResultsTable();
        int tableSize = rt.size();
        rt.deleteRows(row1, row2);
        ImagePlus imp = WindowManager.getCurrentImage();
        if (imp!=null)
            Overlay.updateTableOverlay(imp, row1, row2, tableSize);
        rt.show("Results");
    }
    
    
    public static double getValue(ImagePlus imp, String measurement) {
        String options = "";
        int index = measurement.indexOf(" ");
        if (index>0) {
            if (index<measurement.length()-1)
                options = measurement.substring(index+1, measurement.length());
            measurement = measurement.substring(0, index);
        }
        int measurements = Measurements.ALL_STATS + Measurements.SLICE;
        if (options.contains("limit"))
            measurements += Measurements.LIMIT;
        Calibration cal = null;
        if (options.contains("raw")) {
            cal = imp.getCalibration();
            imp.setCalibration(null);
        }
        ResultsTable rt = new ResultsTable();
        Analyzer analyzer = new Analyzer(imp, measurements, rt);
        analyzer.measure();
        double value = Double.NaN;
        try {
            value = rt.getValue(measurement, 0);
        } catch (Exception e) {};
        if (cal!=null)
            imp.setCalibration(cal);
        return value;
    }
    
    public static TextPanel getTextPanel() {
        if (textPanel==null && ij!=null)
            showResults();
        return textPanel;
    }
    
    
    public static void setTextPanel(TextPanel tp) {
        textPanel = tp;
    }
    
    
    public static void noImage() {
        String msg = "There are no images open";
        if (macroInterpreter!=null) {
            macroInterpreter.abort(msg);
            macroInterpreter = null;
        } else
            error("No Image", msg);
    }
    
    public static void outOfMemory(String name) {
        Undo.reset();
        System.gc();
        lastErrorMessage = "out of memory";
        String tot = Runtime.getRuntime().maxMemory()/1048576L+"MB";
        if (!memMessageDisplayed)
            log(">>>>>>>>>>>>>>>>>>>>>>>>>>>");
        log("<Out of memory>");
        if (!memMessageDisplayed) {
            log("<All available memory ("+tot+") has been>");
            log("<used. To make more available, use the>");
            log("<Edit>Options>Memory & Threads command.>");
            log(">>>>>>>>>>>>>>>>>>>>>>>>>>>");
            memMessageDisplayed = true;
        }
        Macro.abort();
    }
    
    public static void showProgress(double progress) {
        if (progressBar!=null) progressBar.show(progress, false);
    }
    
    public static void showProgress(int currentIndex, int finalIndex) {
        if (progressBar!=null) {
            progressBar.show(currentIndex, finalIndex);
            if (currentIndex==finalIndex)
                progressBar.setBatchMode(false);
        }
    }
    
    public static void showMessage(String msg) {
        showMessage("Message", msg);
    }
    
    public static void showMessage(String title, String msg) {
        if (ij!=null) {
            if (msg!=null && (msg.startsWith("<html>")||msg.startsWith("<HTML>"))) {
                HTMLDialog hd = new HTMLDialog(title, msg);
                if (isMacro() && hd.escapePressed())
                    throw new RuntimeException(Macro.MACRO_CANCELED);
            } else {
                MessageDialog md = new MessageDialog(ij, title, msg); 
                if (isMacro() && md.escapePressed())
                    throw new RuntimeException(Macro.MACRO_CANCELED);
            }
        } else
            System.out.println(msg);
    }
    
    public static void error(String msg) {
        error(null, msg);
        if (Thread.currentThread().getName().endsWith("JavaScript"))
            throw new RuntimeException(Macro.MACRO_CANCELED);
        else
            Macro.abort();
    }
    
    
    public static void error(String title, String msg) {
        if (macroInterpreter!=null) {
            macroInterpreter.abort(msg);
            macroInterpreter = null;
            return;
        }
        if (msg!=null && msg.endsWith(Macro.MACRO_CANCELED))
            return;
        String title2 = title!=null?title:"ImageJ";
        boolean abortMacro = title!=null;
        lastErrorMessage = msg;
        if (redirectErrorMessages) {
            IJ.log(title2 + ": " + msg);
            if (abortMacro && (title.contains("Open")||title.contains("Reader")))
                abortMacro = false;
        } else
            showMessage(title2, msg);
        redirectErrorMessages = false;
        if (abortMacro)
            Macro.abort();
    }
    
    public static void exit() {
        if (Thread.currentThread().getName().endsWith("JavaScript"))
            throw new RuntimeException(Macro.MACRO_CANCELED);
    }
    
    public static String getErrorMessage() {
        String msg = lastErrorMessage;
        lastErrorMessage = null;
        return msg;
    }
    
    public static boolean showMessageWithCancel(String title, String msg) {
        GenericDialog gd = new GenericDialog(title);
        gd.addMessage(msg);
        gd.showDialog();
        return !gd.wasCanceled();
    }
    public static final int CANCELED = Integer.MIN_VALUE;
    
    public static double getNumber(String prompt, double defaultValue) {
        GenericDialog gd = new GenericDialog("");
        int decimalPlaces = (int)defaultValue==defaultValue?0:2;
        gd.addNumericField(prompt, defaultValue, decimalPlaces);
        gd.showDialog();
        if (gd.wasCanceled())
            return CANCELED;
        double v = gd.getNextNumber();
        if (gd.invalidNumber())
            return defaultValue;
        else
            return v;
    }
    
    public static String getString(String prompt, String defaultString) {
        GenericDialog gd = new GenericDialog("");
        gd.addStringField(prompt, defaultString, 20);
        gd.showDialog();
        if (gd.wasCanceled())
            return "";
        return gd.getNextString();
    }
    
    public static void wait(int msecs) {
        try {Thread.sleep(msecs);}
        catch (InterruptedException e) { }
    }
    
    
    public static void beep() {
        java.awt.Toolkit.getDefaultToolkit().beep();
    }
    
    
    public static String freeMemory() {
        long inUse = currentMemory();
        String inUseStr = inUse<10000*1024?inUse/1024L+"K":inUse/1048576L+"MB";
        String maxStr="";
        long max = maxMemory();
        if (max>0L) {
            double percent = inUse*100/max;
            maxStr = " of "+max/1048576L+"MB ("+(percent<1.0?"<1":d2s(percent,0)) + "%)";
        }
        return  inUseStr + maxStr;
    }
    
    
    public static long currentMemory() {
        long freeMem = Runtime.getRuntime().freeMemory();
        long totMem = Runtime.getRuntime().totalMemory();
        return totMem-freeMem;
    }
    
    public static long maxMemory() {
        if (maxMemory==0L) {
            Memory mem = new Memory();
            maxMemory = mem.getMemorySetting();
            if (maxMemory==0L) maxMemory = mem.maxMemory();
        }
        return maxMemory;
    }
    
    public static void showTime(ImagePlus imp, long start, String str) {
        showTime(imp, start, str, 1);
    }
    
    public static void showTime(ImagePlus imp, long start, String str, int nslices) {
        if (Interpreter.isBatchMode())
            return;
        double seconds = (System.currentTimeMillis()-start)/1000.0;
        if (seconds<=0.5 && macroRunning())
            return;
        double pixels = (double)imp.getWidth() * imp.getHeight();
        double rate = pixels*nslices/seconds;
        String str2;
        if (rate>1000000000.0)
            str2 = "";
        else if (rate<1000000.0)
            str2 = ", "+d2s(rate,0)+" pixels/second";
        else
            str2 = ", "+d2s(rate/1000000.0,1)+" million pixels/second";
        showStatus(str+seconds+" seconds"+str2);
    }
    
    
    public static  String time(ImagePlus imp, long startNanoTime) {
        double planes = imp.getStackSize();
        double seconds = (System.nanoTime()-startNanoTime)/1000000000.0;
        double mpixels = imp.getWidth()*imp.getHeight()*planes/1000000.0;
        String time = seconds<1.0?d2s(seconds*1000.0,0)+" ms":d2s(seconds,1)+" seconds";
        return time+", "+d2s(mpixels/seconds,1)+" million pixels/second";
    }
        
    
    public static String d2s(double n) {
        return d2s(n, 2);
    }
    
    
    public static String d2s(double n, int decimalPlaces) {
        if (Double.isNaN(n)||Double.isInfinite(n))
            return ""+n;
        if (n==Float.MAX_VALUE)             return "3.4e38";
        double np = n;
        if (n<0.0) np = -n;
        if (decimalPlaces<0) synchronized(IJ.class) {
            decimalPlaces = -decimalPlaces;
            if (decimalPlaces>9) decimalPlaces=9;
            if (sf==null) {
                if (dfs==null)
                    dfs = new DecimalFormatSymbols(Locale.US);
                sf = new DecimalFormat[10];
                sf[1] = new DecimalFormat("0.0E0",dfs);
                sf[2] = new DecimalFormat("0.00E0",dfs);
                sf[3] = new DecimalFormat("0.000E0",dfs);
                sf[4] = new DecimalFormat("0.0000E0",dfs);
                sf[5] = new DecimalFormat("0.00000E0",dfs);
                sf[6] = new DecimalFormat("0.000000E0",dfs);
                sf[7] = new DecimalFormat("0.0000000E0",dfs);
                sf[8] = new DecimalFormat("0.00000000E0",dfs);
                sf[9] = new DecimalFormat("0.000000000E0",dfs);
            }
            return sf[decimalPlaces].format(n);         }
        if (decimalPlaces<0) decimalPlaces = 0;
        if (decimalPlaces>9) decimalPlaces = 9;
        return df[decimalPlaces].format(n);
    }
    
    public static String d2s(double x, int significantDigits, int maxDigits) {
        double log10 = Math.log10(Math.abs(x));
        double roundErrorAtMax = 0.223*Math.pow(10, -maxDigits);
        int magnitude = (int)Math.ceil(log10+roundErrorAtMax);
        int decimals = x==0 ? 0 : maxDigits - magnitude;
        if (decimals<0 || magnitude<significantDigits+1-maxDigits)
            return IJ.d2s(x, -significantDigits);         else {
            if (decimals>significantDigits)
                decimals = Math.max(significantDigits, decimals-maxDigits+significantDigits);
            return IJ.d2s(x, decimals);
        }
    }
    
    public static String pad(int n, int digits) {
        String str = ""+n;
        while (str.length()<digits)
            str = "0"+str;
        return str;
    }
    
    public static String pad(String s, int digits) {
        String str = ""+s;
        while (str.length()<digits)
            str = "0"+str;
        return str;
    }
    
    public static void register(Class c) {
        if (ij!=null) ij.register(c);
    }
    
    
    public static boolean spaceBarDown() {
        return spaceDown;
    }
    
    public static boolean controlKeyDown() {
        return controlDown;
    }
    
    public static boolean altKeyDown() {
        return altDown;
    }
    
    public static boolean shiftKeyDown() {
        return shiftDown;
    }
    
    public static void setKeyDown(int key) {
        switch (key) {
            case KeyEvent.VK_CONTROL:
                controlDown=true;
                break;
            case KeyEvent.VK_META:
                if (isMacintosh()) controlDown=true;
                break;
            case KeyEvent.VK_ALT:
                altDown=true;
                updateStatus();
                break;
            case KeyEvent.VK_SHIFT:
                shiftDown=true;
                if (debugMode) beep();
                break;
            case KeyEvent.VK_SPACE: {
                spaceDown=true;
                ImageWindow win = WindowManager.getCurrentWindow();
                                break;
            }
            case KeyEvent.VK_ESCAPE: {
                escapePressed = true;
                break;
            }
        }
    }
    public static void setKeyUp(int key) {
        switch (key) {
            case KeyEvent.VK_CONTROL: controlDown=false; break;
            case KeyEvent.VK_META: if (isMacintosh()) controlDown=false; break;
            case KeyEvent.VK_ALT: altDown=false; updateStatus(); break;
            case KeyEvent.VK_SHIFT: shiftDown=false; if (debugMode) beep(); break;
            case KeyEvent.VK_SPACE:
                spaceDown=false;
                ImageWindow win = WindowManager.getCurrentWindow();
                                break;
            case ALL_KEYS:
                shiftDown=controlDown=altDown=spaceDown=false;
                break;
        }
    }
    
    private static void updateStatus() {
        ImagePlus imp = WindowManager.getCurrentImage();
        if (imp!=null) {
            Roi roi = imp.getRoi();
            if (roi!=null && imp.getCalibration().scaled()) {
                roi.showStatus();
            }
        }
    }
    public static void setInputEvent(InputEvent e) {
        altDown = e.isAltDown();
        shiftDown = e.isShiftDown();
    }
    
    public static boolean isMacintosh() {
        return isMac;
    }
    
    
    public static boolean isMacOSX() {
        return isMacintosh();
    }
    
    public static boolean isWindows() {
        return isWin;
    }
    
    
    public static int javaVersion() {
        return javaVersion;
    }
    
    
    public static boolean isJava2() {
        return true;
    }
    
    
    public static boolean isJava14() {
        return true;
    }
    
    public static boolean isJava15() {
        return true;
    }
    
    public static boolean isJava16() {
        return javaVersion >= 6;
    }
    
    public static boolean isJava17() {
        return javaVersion >= 7;
    }
    
    public static boolean isJava18() {
        return javaVersion >= 8;
    }
    
    public static boolean isJava19() {
        return javaVersion >= 9;
    }
    
    public static boolean isLinux() {
        return isLinux;
    }
    
    public static boolean isVista() {
        return false;
    }
    
    
    public static boolean is64Bit() {
        if (osarch==null)
            osarch = System.getProperty("os.arch");
        return osarch!=null && osarch.indexOf("64")!=-1;
    }
    
    public static boolean versionLessThan(String version) {
        boolean lessThan = ImageJ.VERSION.compareTo(version)<0;
        if (lessThan)
            error("This plugin or macro requires ImageJ "+version+" or later. Use\nHelp>Update ImageJ to upgrade to the latest version.");
        return lessThan;
    }
    
    
    public static int setupDialog(ImagePlus imp, int flags) {
        if (imp==null || (ij!=null&&ij.hotkey)) {
            if (ij!=null) ij.hotkey=false;
            return flags;
        }
        int stackSize = imp.getStackSize();
        if (stackSize>1) {
            String macroOptions = Macro.getOptions();
            if (imp.isComposite() && ((CompositeImage)imp).getMode()==IJ.COMPOSITE) {
                if (macroOptions==null || !macroOptions.contains("slice"))
                    return flags | PlugInFilter.DOES_STACKS;
            }
            if (macroOptions!=null) {
                if (macroOptions.indexOf("stack ")>=0)
                    return flags | PlugInFilter.DOES_STACKS;
                else
                    return flags;
            }
            if (hideProcessStackDialog)
                return flags;
            String note = ((flags&PlugInFilter.NO_CHANGES)==0)?" There is\nno Undo if you select \"Yes\".":"";
            YesNoCancelDialog d = new YesNoCancelDialog(getInstance(),
                "Process Stack?", "Process all "+stackSize+" images?"+note);
            if (d.cancelPressed())
                return PlugInFilter.DONE;
            else if (d.yesPressed()) {
                if (imp.getStack().isVirtual() && ((flags&PlugInFilter.NO_CHANGES)==0)) {
                    int size = (stackSize*imp.getWidth()*imp.getHeight()*imp.getBytesPerPixel()+524288)/1048576;
                    String msg =
                        "Use the Process>Batch>Virtual Stack command\n"+
                        "to process a virtual stack or convert it into a\n"+
                        "normal stack using Image>Duplicate, which\n"+
                        "will require "+size+"MB of additional memory.";
                    error(msg);
                    return PlugInFilter.DONE;
                }
                if (Recorder.record)
                    Recorder.recordOption("stack");
                return flags | PlugInFilter.DOES_STACKS;
            }
            if (Recorder.record)
                Recorder.recordOption("slice");
        }
        return flags;
    }
        
    
    public static void makeRectangle(int x, int y, int width, int height) {
        if (width<=0 || height<0)
            getImage().deleteRoi();
        else {
            ImagePlus img = getImage();
            if (Interpreter.isBatchMode())
                img.setRoi(new Roi(x,y,width,height), false);
            else
                img.setRoi(x, y, width, height);
        }
    }
    
    
    public static void makeRectangle(double x, double y, double width, double height) {
        if (width<=0 || height<0)
            getImage().deleteRoi();
        else
            getImage().setRoi(new Roi(x,y,width,height), !Interpreter.isBatchMode());
    }
    
    public static void makeOval(int x, int y, int width, int height) {
        if (width<=0 || height<0)
            getImage().deleteRoi();
        else {
            ImagePlus img = getImage();
            img.setRoi(new OvalRoi(x, y, width, height));
        }
    }
    
    
    public static void makeOval(double x, double y, double width, double height) {
        if (width<=0 || height<0)
            getImage().deleteRoi();
        else
            getImage().setRoi(new OvalRoi(x, y, width, height));
    }
    
    public static void makeLine(int x1, int y1, int x2, int y2) {
        getImage().setRoi(new Line(x1, y1, x2, y2));
    }
    
    
    public static void makeLine(double x1, double y1, double x2, double y2) {
        getImage().setRoi(new Line(x1, y1, x2, y2));
    }
    
    public static void makePoint(int x, int y) {
        ImagePlus img = getImage();
        Roi roi = img.getRoi();
        if (shiftKeyDown() && roi!=null && roi.getType()==Roi.POINT) {
            Polygon p = roi.getPolygon();
            p.addPoint(x, y);
            img.setRoi(new PointRoi(p.xpoints, p.ypoints, p.npoints));
            IJ.setKeyUp(KeyEvent.VK_SHIFT);
        } else if (altKeyDown() && roi!=null && roi.getType()==Roi.POINT) {
            ((PolygonRoi)roi).deleteHandle(x, y);
            IJ.setKeyUp(KeyEvent.VK_ALT);
        } else
            img.setRoi(new PointRoi(x, y));
    }
    
    
    public static void makePoint(double x, double y) {
        ImagePlus img = getImage();
        Roi roi = img.getRoi();
        if (shiftKeyDown() && roi!=null && roi.getType()==Roi.POINT) {
            Polygon p = roi.getPolygon();
            p.addPoint((int)Math.round(x), (int)Math.round(y));
            img.setRoi(new PointRoi(p.xpoints, p.ypoints, p.npoints));
            IJ.setKeyUp(KeyEvent.VK_SHIFT);
        } else if (altKeyDown() && roi!=null && roi.getType()==Roi.POINT) {
            ((PolygonRoi)roi).deleteHandle(x, y);
            IJ.setKeyUp(KeyEvent.VK_ALT);
        } else
            img.setRoi(new PointRoi(x, y));
    }
    
    public static Roi Roi(double x, double y, double width, double height) {
        return new Roi(x, y, width, height);
    }
    
    public static OvalRoi OvalRoi(double x, double y, double width, double height) {
        return new OvalRoi(x, y, width, height);
    }
    
    public static void setMinAndMax(double min, double max) {
        setMinAndMax(getImage(), min, max, 7);
    }
    
    public static void setMinAndMax(ImagePlus img, double min, double max) {
        setMinAndMax(img, min, max, 7);
    }
    
    public static void setMinAndMax(double min, double max, int channels) {
        setMinAndMax(getImage(), min, max, channels);
    }
    private static void setMinAndMax(ImagePlus img, double min, double max, int channels) {
        Calibration cal = img.getCalibration();
        min = cal.getRawValue(min); 
        max = cal.getRawValue(max);
        if (channels==7)
            img.setDisplayRange(min, max);
        else
            img.setDisplayRange(min, max, channels);
        img.updateAndDraw();
    }
    
    public static void resetMinAndMax() {
        resetMinAndMax(getImage());
    }
    
    public static void resetMinAndMax(ImagePlus img) {
        img.resetDisplayRange();
        img.updateAndDraw();
    }
    
    public static void setThreshold(double lowerThreshold, double upperThresold) {
        setThreshold(lowerThreshold, upperThresold, null);
    }
    
    
    public static void setThreshold(double lowerThreshold, double upperThreshold, String displayMode) {
        setThreshold(getImage(), lowerThreshold, upperThreshold, displayMode);
    }
    
    public static void setThreshold(ImagePlus img, double lowerThreshold, double upperThreshold) {
        setThreshold(img, lowerThreshold, upperThreshold, "Red");
    }
    
    public static void setThreshold(ImagePlus img, double lowerThreshold, double upperThreshold, String displayMode) {
        Calibration cal = img.getCalibration();
        if (displayMode==null || !displayMode.contains("raw")) {
            lowerThreshold = cal.getRawValue(lowerThreshold); 
            upperThreshold = cal.getRawValue(upperThreshold);
        }
        setRawThreshold(img, lowerThreshold, upperThreshold, displayMode);
    }
    
    public static void setRawThreshold(ImagePlus img, double lowerThreshold, double upperThreshold) {
        setRawThreshold(img, lowerThreshold, upperThreshold, null);
    }
    
    public static void setRawThreshold(ImagePlus img, double lowerThreshold, double upperThreshold, String displayMode) {
        int mode = ImageProcessor.RED_LUT;
        if (displayMode!=null) {
            displayMode = displayMode.toLowerCase(Locale.US);
            if (displayMode.contains("black"))
                mode = ImageProcessor.BLACK_AND_WHITE_LUT;
            else if (displayMode.contains("over"))
                mode = ImageProcessor.OVER_UNDER_LUT;
            else if (displayMode.contains("no"))
                mode = ImageProcessor.NO_LUT_UPDATE;
        }
        img.getProcessor().setThreshold(lowerThreshold, upperThreshold, mode);
        if (mode!=ImageProcessor.NO_LUT_UPDATE && img.getWindow()!=null) {
            img.getProcessor().setLutAnimation(true);
            img.updateAndDraw();
            ThresholdAdjuster.update();
        }
    }
    public static void setAutoThreshold(ImagePlus imp, String method) {
        ImageProcessor ip = imp.getProcessor();
        if (ip instanceof ColorProcessor)
            throw new IllegalArgumentException("Non-RGB image required");
        ip.setRoi(imp.getRoi());
        if (method!=null) {
            try {
                if (method.indexOf("stack")!=-1)
                    setStackThreshold(imp, ip, method);
                else
                    ip.setAutoThreshold(method);
            } catch (Exception e) {
                log(e.getMessage());
            }
        } else
            ip.setAutoThreshold(ImageProcessor.ISODATA2, ImageProcessor.RED_LUT);
        imp.updateAndDraw();
    }
    
    private static void setStackThreshold(ImagePlus imp, ImageProcessor ip, String method) {
        boolean darkBackground = method.indexOf("dark")!=-1;
        int measurements = Analyzer.getMeasurements();
        Analyzer.setMeasurements(Measurements.AREA+Measurements.MIN_MAX);
        ImageStatistics stats = new StackStatistics(imp);
        Analyzer.setMeasurements(measurements);
        AutoThresholder thresholder = new AutoThresholder();
        double min=0.0, max=255.0;
        if (imp.getBitDepth()!=8) {
            min = stats.min;
            max = stats.max;
        }
        int threshold = thresholder.getThreshold(method, stats.histogram);
        double lower, upper;
        if (darkBackground) {
            if (ip.isInvertedLut())
                {lower=0.0; upper=threshold;}
            else
                {lower=threshold+1; upper=255.0;}
        } else {
            if (ip.isInvertedLut())
                {lower=threshold+1; upper=255.0;}
            else
                {lower=0.0; upper=threshold;}
        }
        if (lower>255) lower = 255;
        if (max>min) {
            lower = min + (lower/255.0)*(max-min);
            upper = min + (upper/255.0)*(max-min);
        } else
            lower = upper = min;
        ip.setMinAndMax(min, max);
        ip.setThreshold(lower, upper, ImageProcessor.RED_LUT);
        imp.updateAndDraw();
    }
    
    public static void resetThreshold() {
        resetThreshold(getImage());
    }
    
    
    public static void resetThreshold(ImagePlus img) {
        ImageProcessor ip = img.getProcessor();
        ip.resetThreshold();
        ip.setLutAnimation(true);
        img.updateAndDraw();
        ThresholdAdjuster.update();
    }
    
    
    public static void selectWindow(int id) {
        if (id>0)
            id = WindowManager.getNthImageID(id);
        ImagePlus imp = WindowManager.getImage(id);
        if (imp==null)
            error("Macro Error", "Image "+id+" not found or no images are open.");
        if (Interpreter.isBatchMode()) {
            ImagePlus impT = WindowManager.getTempCurrentImage();
            ImagePlus impC = WindowManager.getCurrentImage();
            if (impC!=null && impC!=imp && impT!=null)
                impC.saveRoi();
            WindowManager.setTempCurrentImage(imp);
            Interpreter.activateImage(imp);
            WindowManager.setWindow(null);
        } else {
            if (imp==null)
                return;
            ImageWindow win = imp.getWindow();
            if (win!=null) {
                win.toFront();
                win.setState(Frame.NORMAL);
                WindowManager.setWindow(win);
            }
            long start = System.currentTimeMillis();
                        String thread = Thread.currentThread().getName();
            int timeout = thread!=null&&thread.indexOf("EventQueue")!=-1?0:1000;
            if (IJ.isMacOSX() && IJ.isJava18() && timeout>0)
                timeout = 250;              while (true) {
                wait(10);
                imp = WindowManager.getCurrentImage();
                if (imp!=null && imp.getID()==id)
                    return;                 if ((System.currentTimeMillis()-start)>timeout && win!=null) {
                    WindowManager.setCurrentWindow(win);
                    return;
                }
            }
        }
    }
    
    public static void selectWindow(String title) {
        if (title.equals("ImageJ")&&ij!=null) {
            ij.toFront();
            return;
        }
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis()-start<3000) {             Window win = WindowManager.getWindow(title);
            if (win!=null && !(win instanceof ImageWindow)) {
                selectWindow(win);
                return;
            }
            int[] wList = WindowManager.getIDList();
            int len = wList!=null?wList.length:0;
            for (int i=0; i<len; i++) {
                ImagePlus imp = WindowManager.getImage(wList[i]);
                if (imp!=null) {
                    if (imp.getTitle().equals(title)) {
                        selectWindow(imp.getID());
                        return;
                    }
                }
            }
            wait(10);
        }
        error("Macro Error", "No window with the title \""+title+"\" found.");
    }
    
    static void selectWindow(Window win) {
        if (win instanceof Frame) {
            ((Frame)win).toFront();
            ((Frame)win).setState(Frame.NORMAL);
        } else
            ((Dialog)win).toFront();
        long start = System.currentTimeMillis();
        while (true) {
            wait(10);
            if (WindowManager.getActiveWindow()==win)
                return;             if ((System.currentTimeMillis()-start)>1000) {
                WindowManager.setWindow(win);
                return;               }
        }
    }
    
    
    public static void setForegroundColor(int red, int green, int blue) {
        setColor(red, green, blue, true);
    }
    
    public static void setBackgroundColor(int red, int green, int blue) {
        setColor(red, green, blue, false);
    }
    
    static void setColor(int red, int green, int blue, boolean foreground) {
        Color c = Colors.toColor(red, green, blue);
        if (foreground) {
            Toolbar.setForegroundColor(c);
            ImagePlus img = WindowManager.getCurrentImage();
            if (img!=null)
                img.getProcessor().setColor(c);
        } else
            Toolbar.setBackgroundColor(c);
    }
    
    public static void setTool(int id) {
        Toolbar.getInstance().setTool(id);
    }
    
    public static boolean setTool(String name) {
        return Toolbar.getInstance().setTool(name);
    }
    
    public static String getToolName() {
        return Toolbar.getToolName();
    }
    
    public static int doWand(int x, int y) {
        return doWand(getImage(), x, y, 0, null);
    }
    
    public static int doWand(int x, int y, double tolerance, String mode) {
        return doWand(getImage(), x, y, tolerance, mode);
    }
    
    
    public static int doWand(ImagePlus img, int x, int y, double tolerance, String mode) {
        ImageProcessor ip = img.getProcessor();
        if ((img.getType()==ImagePlus.GRAY32) && Double.isNaN(ip.getPixelValue(x,y)))
            return 0;
        int imode = Wand.LEGACY_MODE;
        boolean smooth = false;
        if (mode!=null) {
            if (mode.startsWith("4"))
                imode = Wand.FOUR_CONNECTED;
            else if (mode.startsWith("8"))
                imode = Wand.EIGHT_CONNECTED;
            smooth = mode.contains("smooth");
                
        }
        Wand w = new Wand(ip);
        double t1 = ip.getMinThreshold();
        if (t1==ImageProcessor.NO_THRESHOLD || (ip.getLutUpdateMode()==ImageProcessor.NO_LUT_UPDATE&& tolerance>0.0)) {
            w.autoOutline(x, y, tolerance, imode);
            smooth = false;
        } else
            w.autoOutline(x, y, t1, ip.getMaxThreshold(), imode);
        if (w.npoints>0) {
            Roi previousRoi = img.getRoi();
            Roi roi = new PolygonRoi(w.xpoints, w.ypoints, w.npoints, Roi.TRACED_ROI);
            img.deleteRoi();
            img.setRoi(roi);            
            if (previousRoi!=null)
                roi.update(shiftKeyDown(), altKeyDown());              Roi roi2 = img.getRoi();
            if (smooth && roi2!=null && roi2.getType()==Roi.TRACED_ROI) {
                Rectangle bounds = roi2.getBounds();
                if (bounds.width>1 && bounds.height>1) {
                    if (smoothMacro==null)
                        smoothMacro = BatchProcessor.openMacroFromJar("SmoothWandTool.txt");
                    if (EventQueue.isDispatchThread())
                        new MacroRunner(smoothMacro);                     else
                        Macro.eval(smoothMacro);
                }
            }
        }
        return w.npoints;
    }
    
    
    public static void setPasteMode(String mode) {
        Roi.setPasteMode(stringToPasteMode(mode));
    }
    public static int stringToPasteMode(String mode) {
        if (mode==null)
            return Blitter.COPY;
        mode = mode.toLowerCase(Locale.US);
        int m = Blitter.COPY;
        if (mode.startsWith("ble") || mode.startsWith("ave"))
            m = Blitter.AVERAGE;
        else if (mode.startsWith("diff"))
            m = Blitter.DIFFERENCE;
        else if (mode.indexOf("zero")!=-1)
            m = Blitter.COPY_ZERO_TRANSPARENT;
        else if (mode.startsWith("tran"))
            m = Blitter.COPY_TRANSPARENT;
        else if (mode.startsWith("and"))
            m = Blitter.AND;
        else if (mode.startsWith("or"))
            m = Blitter.OR;
        else if (mode.startsWith("xor"))
            m = Blitter.XOR;
        else if (mode.startsWith("sub"))
            m = Blitter.SUBTRACT;
        else if (mode.startsWith("add"))
            m = Blitter.ADD;
        else if (mode.startsWith("div"))
            m = Blitter.DIVIDE;
        else if (mode.startsWith("mul"))
            m = Blitter.MULTIPLY;
        else if (mode.startsWith("min"))
            m = Blitter.MIN;
        else if (mode.startsWith("max"))
            m = Blitter.MAX;
        return m;
    }
    
    public static ImagePlus getImage() {
        ImagePlus img = WindowManager.getCurrentImage();
        if (img==null) {
            IJ.noImage();
            if (ij==null)
                System.exit(0);
            else
                abort();
        }
        return img;
    }
    
    
    public static ImagePlus getImage(Interpreter interpreter) {
        macroInterpreter = interpreter;
        ImagePlus imp =  getImage();
        macroInterpreter = null;
        return imp;
    }
    
    
    public static ImageProcessor getProcessor() {
        ImagePlus imp = IJ.getImage();
        return imp.getProcessor();
    }
    
    public static void setSlice(int slice) {
        getImage().setSlice(slice);
    }
    
    public static String getVersion() {
        return ImageJ.VERSION;
    }
    
    
    public static String getFullVersion() {
        String build = ImageJ.BUILD;
        if (build.length()==0)
            build = "99";
        else if (build.length()==1)
            build = "0" + build;
        return ImageJ.VERSION+build;
    }
        
    
    public static String getDirectory(String title) {
        String dir = null;
        String title2 = title.toLowerCase(Locale.US);
        if (title2.equals("plugins"))
            dir = Menus.getPlugInsPath();
        else if (title2.equals("macros"))
            dir = Menus.getMacrosPath();
        else if (title2.equals("luts")) {
            String ijdir = Prefs.getImageJDir();
            if (ijdir!=null)
                dir = ijdir + "luts" + File.separator;
            else
                dir = null;
        } else if (title2.equals("home"))
            dir = System.getProperty("user.home");
        else if (title2.equals("downloads"))
            dir = System.getProperty("user.home")+File.separator+"Downloads";
        else if (title2.equals("startup"))
            dir = Prefs.getImageJDir();
        else if (title2.equals("imagej"))
            dir = Prefs.getImageJDir();
        else if (title2.equals("current") || title2.equals("default"))
            dir = OpenDialog.getDefaultDirectory();
        else if (title2.equals("preferences"))
            dir = Prefs.getPrefsDir();
        else if (title2.equals("temp")) {
            dir = System.getProperty("java.io.tmpdir");
            if (isMacintosh()) dir = "/tmp/";
        } else if (title2.equals("image")) {
            ImagePlus imp = WindowManager.getCurrentImage();
            FileInfo fi = imp!=null?imp.getOriginalFileInfo():null;
            if (fi!=null && fi.directory!=null) {
                dir = fi.directory;
            } else
                dir = null;
        } else if (title2.equals("file"))
            dir = OpenDialog.getLastDirectory();
        else if (title2.equals("cwd"))
            dir = System.getProperty("user.dir");
        else {
            DirectoryChooser dc = new DirectoryChooser(title);
            dir = dc.getDirectory();
            if (dir==null) Macro.abort();
        }
        dir = addSeparator(dir);
        return dir;
    }
    
    public static String addSeparator(String path) {
        if (path==null)
            return null;
        if (path.length()>0 && !(path.endsWith(File.separator)||path.endsWith("/"))) {
            if (IJ.isWindows()&&path.contains(File.separator))
                path += File.separator;
            else
                path += "/";
        }
        return path;
    }
        
    
    public static String getDir(String title) {
        return getDirectory(title);
    }
    
    
    public static String getFilePath(String dialogTitle) {
        OpenDialog od = new OpenDialog(dialogTitle);
        return od.getPath();
    }
        
    
    public static void open() {
        open(null);
    }
    
    public static void open(String path) {
        if (ij==null && Menus.getCommands()==null)
            init();
        Opener o = new Opener();
        macroRunning = true;
        if (path==null || path.equals(""))      
            o.open();
        else
            o.open(path);
        macroRunning = false;
    }
        
    
    public static void open(String path, int n) {
        if (ij==null && Menus.getCommands()==null)
            init();
        ImagePlus imp = openImage(path, n);
        if (imp!=null) imp.show();
    }
    
    public static ImagePlus openImage(String path) {
        macroRunning = true;
        ImagePlus imp = (new Opener()).openImage(path);
        macroRunning = false;
        return imp;
    }
    
    public static ImagePlus openImage(String path, int n) {
        return (new Opener()).openImage(path, n);
    }
    
    public static ImagePlus openVirtual(String path) {
        return FileInfoVirtualStack.openVirtual(path);
    }
    
    public static ImagePlus openImage() {
        return openImage(null);
    }
    
    public static String openUrlAsString(String url) {
                url = Opener.updateUrl(url);
        if (debugMode) log("OpenUrlAsString: "+url);
        StringBuffer sb = null;
        url = url.replaceAll(" ", "%20");
        try {
                        URL u = new URL(url);
            URLConnection uc = u.openConnection();
            long len = uc.getContentLength();
            if (len>5242880L)
                return "<Error: file is larger than 5MB>";
            InputStream in = u.openStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(in,"UTF-8"));
            sb = new StringBuffer() ;
            String line;
            while ((line=br.readLine()) != null)
                sb.append (line + "\n");
            in.close ();
        } catch (Exception e) {
            return("<Error: "+e+">");
        }
        if (sb!=null)
            return new String(sb);
        else
            return "";
    }
    
    
    
    
    
    public static void save(String path) {
        save(null, path);
    }
    
    public static void save(ImagePlus imp, String path) {
        ImagePlus imp2 = imp;
        if (imp2==null)
            imp2 = WindowManager.getCurrentImage();
        int dotLoc = path.lastIndexOf('.');
        if (dotLoc==-1 && imp2!=null) {
            path = path + ".tif";             dotLoc = path.lastIndexOf('.');
        }
        if (dotLoc!=-1) {
            String title = imp2!=null?imp2.getTitle():null;
            saveAs(imp, path.substring(dotLoc+1), path);
            if (title!=null)
                imp2.setTitle(title);
        } else
            error("The file path passed to IJ.save() method or save()\nmacro function is missing the required extension.\n \n\""+path+"\"");
    }
    
    public static void saveAs(String format, String path) {
        saveAs(null, format, path);
    }
    
    public static void saveAs(ImagePlus imp, String format, String path) {
        if (format==null)
            return;
        if (path!=null && path.length()==0)
            path = null;
        format = format.toLowerCase(Locale.US);
        Roi roi2 = imp!=null?imp.getRoi():null;
        if (roi2!=null)
            roi2.endPaste();
        if (format.indexOf("tif")!=-1) {
            saveAsTiff(imp, path);
            return;
        } else if (format.indexOf("jpeg")!=-1 || format.indexOf("jpg")!=-1) {
            path = updateExtension(path, ".jpg");
            JpegWriter.save(imp, path, FileSaver.getJpegQuality());
            return;
        } else if (format.indexOf("gif")!=-1) {
            path = updateExtension(path, ".gif");
            GifWriter.save(imp, path);
            return;
        } else if (format.indexOf("text image")!=-1) {
            path = updateExtension(path, ".txt");
            format = "Text Image...";
        } else if (format.indexOf("text")!=-1 || format.indexOf("txt")!=-1) {
            if (path!=null && !path.endsWith(".xls") && !path.endsWith(".csv") && !path.endsWith(".tsv"))
                path = updateExtension(path, ".txt");
            format = "Text...";
        } else if (format.indexOf("zip")!=-1) {
            path = updateExtension(path, ".zip");
            format = "ZIP...";
        } else if (format.indexOf("raw")!=-1) {
                        format = "Raw Data...";
        } else if (format.indexOf("avi")!=-1) {
            path = updateExtension(path, ".avi");
            format = "AVI... ";
        } else if (format.indexOf("bmp")!=-1) {
            path = updateExtension(path, ".bmp");
            format = "BMP...";
        } else if (format.indexOf("fits")!=-1) {
            path = updateExtension(path, ".fits");
            format = "FITS...";
        } else if (format.indexOf("png")!=-1) {
            path = updateExtension(path, ".png");
            format = "PNG...";
        } else if (format.indexOf("pgm")!=-1) {
            path = updateExtension(path, ".pgm");
            format = "PGM...";
        } else if (format.indexOf("lut")!=-1) {
            path = updateExtension(path, ".lut");
            format = "LUT...";
        } else if (format.contains("results") || format.contains("measurements") || format.contains("table")) {
            format = "Results...";
        } else if (format.contains("selection") || format.contains("roi")) {
            path = updateExtension(path, ".roi");
            format = "Selection...";
        } else if (format.indexOf("xy")!=-1 || format.indexOf("coordinates")!=-1) {
            path = updateExtension(path, ".txt");
            format = "XY Coordinates...";
        } else
            error("Unsupported save() or saveAs() file format: \""+format+"\"\n \n\""+path+"\"");
        if (path==null)
            run(format);
        else {
            if (path.contains(" "))
                run(imp, format, "save=["+path+"]");
            else
                run(imp, format, "save="+path);
        }
    }
    
    
    public static boolean saveAsTiff(ImagePlus imp, String path) {
        if (imp==null)
            imp = getImage();
        if (path==null || path.equals(""))
            return (new FileSaver(imp)).saveAsTiff();
        if (!path.endsWith(".tiff"))
            path = updateExtension(path, ".tif");
        FileSaver fs = new FileSaver(imp);
        boolean ok;
        if (imp.getStackSize()>1)
            ok = fs.saveAsTiffStack(path);
        else
            ok = fs.saveAsTiff(path);
        if (ok)
            fs.updateImagePlus(path, FileInfo.TIFF);
        return ok;
    }
    
    static String updateExtension(String path, String extension) {
        if (path==null) return null;
        int dotIndex = path.lastIndexOf(".");
        int separatorIndex = path.lastIndexOf(File.separator);
        if (dotIndex>=0 && dotIndex>separatorIndex && (path.length()-dotIndex)<=5) {
            if (dotIndex+1<path.length() && Character.isDigit(path.charAt(dotIndex+1)))
                path += extension;
            else
                path = path.substring(0, dotIndex) + extension;
        } else
            path += extension;
        return path;
    }
    
    
    public static String saveString(String string, String path) {
        return write(string, path, false);
    }
    
    public static String append(String string, String path) {
        return write(string+"\n", path, true);
    }
    private static String write(String string, String path, boolean append) {
        if (path==null || path.equals("")) {
            String msg = append?"Append String...":"Save String...";
            SaveDialog sd = new SaveDialog(msg, "Untitled", ".txt");
            String name = sd.getFileName();
            if (name==null) return null;
            path = sd.getDirectory() + name;
        }
        try {
            BufferedWriter out = new BufferedWriter(new FileWriter(path, append));
            out.write(string);
            out.close();
        } catch (IOException e) {
            return ""+e;
        }
        return null;
    }
    
    public static String openAsString(String path) {
        if (path==null || path.equals("")) {
            OpenDialog od = new OpenDialog("Open Text File", "");
            String directory = od.getDirectory();
            String name = od.getFileName();
            if (name==null) return null;
            path = directory + name;
        }
        String str = "";
        File file = new File(path);
        if (!file.exists())
            return "Error: file not found";
        try {
            StringBuffer sb = new StringBuffer(5000);
            BufferedReader r = new BufferedReader(new FileReader(file));
            while (true) {
                String s=r.readLine();
                if (s==null)
                    break;
                else
                    sb.append(s+"\n");
            }
            r.close();
            str = new String(sb);
        }
        catch (Exception e) {
            str = "Error: "+e.getMessage();
        }
        return str;
    }
    
    public static ByteBuffer openAsByteBuffer(String path) {
        if (path==null || path.equals("")) {
            OpenDialog od = new OpenDialog("Open as ByteBuffer", "");
            String directory = od.getDirectory();
            String name = od.getFileName();
            if (name==null) return null;
            path = directory + name;
        }
        File file = new File(path);
        if (!file.exists()) {
            error("OpenAsByteBuffer", "File not found");
            return null;
        }
        int len = (int)file.length();
        byte[] buffer = new byte[len];
        try {
            InputStream in = new BufferedInputStream(new FileInputStream(path));
            DataInputStream dis = new DataInputStream(in);
            dis.readFully(buffer);
            dis.close();
        }
        catch (Exception e) {
            error("OpenAsByteBuffer", e.getMessage());
            return null;
        }
        return ByteBuffer.wrap(buffer);
    }
    
     public static ImagePlus createImage(String title, int width, int height, int depth, int bitdepth) {
        return NewImage.createImage(title, width, height, depth, bitdepth, NewImage.FILL_BLACK);
     }
     
     public static ImagePlus createImage(String title, String type, int width, int height, int depth) {
        type = type.toLowerCase(Locale.US);
        int bitDepth = 8;
        if (type.contains("16"))
            bitDepth = 16;
        boolean signedInt = type.contains("32-bit int");
        if (type.contains("32"))
            bitDepth = 32;
        if (type.contains("24") || type.contains("rgb") || signedInt)
            bitDepth = 24;
        int options = NewImage.FILL_WHITE;
        if (bitDepth==16 || bitDepth==32)
            options = NewImage.FILL_BLACK;
        if (type.contains("white"))
            options = NewImage.FILL_WHITE;
        else if (type.contains("black"))
            options = NewImage.FILL_BLACK;
        else if (type.contains("ramp"))
            options = NewImage.FILL_RAMP;
        else if (type.contains("noise") || type.contains("random"))
            options = NewImage.FILL_NOISE;
        options += NewImage.CHECK_AVAILABLE_MEMORY;
        if (signedInt)
            options += NewImage.SIGNED_INT;
        return NewImage.createImage(title, width, height, depth, bitDepth, options);
    }
    
     public static ImagePlus createImage(String title, String type, int width, int height, int channels, int slices, int frames) {
        if (type.contains("label"))
            type += "ramp";
        if (!(type.contains("white")||type.contains("ramp")))
            type += "black";
        ImagePlus imp = IJ.createImage(title, type, width, height, channels*slices*frames);
        imp.setDimensions(channels, slices, frames);
        int mode = IJ.COLOR;
        if (type.contains("composite"))
            mode = IJ.COMPOSITE;
        if (type.contains("grayscale"))
            mode = IJ.GRAYSCALE;
        if (channels>1 && imp.getBitDepth()!=24)
            imp = new CompositeImage(imp, mode);
        imp.setOpenAsHyperStack(true);
        if (type.contains("label"))
            HyperStackMaker.labelHyperstack(imp);
        return imp;
     }
    
     public static ImagePlus createHyperStack(String title, int width, int height, int channels, int slices, int frames, int bitdepth) {
        ImagePlus imp = createImage(title, width, height, channels*slices*frames, bitdepth);
        imp.setDimensions(channels, slices, frames);
        if (channels>1 && bitdepth!=24)
            imp = new CompositeImage(imp, IJ.COMPOSITE);
        imp.setOpenAsHyperStack(true);
        return imp;
     }
     
     
    public static void newImage(String title, String type, int width, int height, int depth) {
        ImagePlus imp = createImage(title, type, width, height, depth);
        if (imp!=null) {
            macroRunning = true;
            imp.show();
            macroRunning = false;
        }
    }
    
    public static boolean escapePressed() {
        return escapePressed;
    }
    
    public static void resetEscape() {
        escapePressed = false;
    }
    
    
    public static void redirectErrorMessages() {
        redirectErrorMessages = true;
        lastErrorMessage = null;
    }
    
    
    public static void redirectErrorMessages(boolean redirect) {
        redirectErrorMessages = redirect;
        lastErrorMessage = null;
    }
    
    public static boolean redirectingErrorMessages() {
        return redirectErrorMessages;
    }
    
    public static void suppressPluginNotFoundError() {
        suppressPluginNotFoundError = true;
    }
    
    public static ClassLoader getClassLoader() {
        if (classLoader==null) {
            String pluginsDir = Menus.getPlugInsPath();
            if (pluginsDir==null) {
                String home = System.getProperty("plugins.dir");
                if (home!=null) {
                    if (!home.endsWith(Prefs.separator)) home+=Prefs.separator;
                    pluginsDir = home+"plugins"+Prefs.separator;
                    if (!(new File(pluginsDir)).isDirectory())
                        pluginsDir = home;
                }
            }
            if (pluginsDir==null)
                return IJ.class.getClassLoader();
            else {
                if (Menus.jnlp)
                    classLoader = new PluginClassLoader(pluginsDir, true);
                else
                    classLoader = new PluginClassLoader(pluginsDir);
            }
        }
        return classLoader;
    }
    
    
    public static Dimension getScreenSize() {
        Rectangle bounds = GUI.getScreenBounds();
        return new Dimension(bounds.width, bounds.height);
    }
    
    
    public static String[] getLuts() {
        ArrayList list = new ArrayList();
        Hashtable commands = Menus.getCommands();
        Menu lutsMenu = Menus.getImageJMenu("Image>Lookup Tables");
        if (commands==null || lutsMenu==null)
            return new String[0];
        for (int i=0; i<lutsMenu.getItemCount(); i++) {
            MenuItem menuItem = lutsMenu.getItem(i);
            String label = menuItem.getLabel();
            if (label.equals("-") || label.equals("Invert LUT") || label.equals("Apply LUT"))
                continue;
            String command = (String)commands.get(label);
            if (command==null || command.startsWith("ij.plugin.LutLoader"))
                list.add(label);
        }
        return (String[])list.toArray(new String[list.size()]);
    }
    
    static void abort() {
        if ((ij!=null || Interpreter.isBatchMode()) && macroInterpreter==null)
            throw new RuntimeException(Macro.MACRO_CANCELED);
    }
    
    static void setClassLoader(ClassLoader loader) {
        classLoader = loader;
    }
    public static void resetClassLoader() {
        setClassLoader(null);
    }
    
    public static void handleException(Throwable e) {
        if (exceptionHandler!=null) {
            exceptionHandler.handle(e);
            return;
        }
        if (Macro.MACRO_CANCELED.equals(e.getMessage()))
            return;
        CharArrayWriter caw = new CharArrayWriter();
        PrintWriter pw = new PrintWriter(caw);
        e.printStackTrace(pw);
        String s = caw.toString();
        String lineNumber = "";
        if (s!=null && s.contains("ThreadDeath"))
            return;
        Interpreter interpreter = Thread.currentThread().getName().endsWith("Macro$") ? Interpreter.getInstance() : null;
        if (interpreter!=null)
            lineNumber = "\nMacro line number: " + interpreter.getLineNumber();
        if (getInstance()!=null) {
            s = IJ.getInstance().getInfo()+lineNumber+"\n \n"+s;
            new TextWindow("Exception", s, 500, 340);
        } else
            log(s);
    }
    
    public static void setExceptionHandler(ExceptionHandler handler) {
        exceptionHandler = handler;
    }
    public interface ExceptionHandler {
        public void handle(Throwable e);
    }
    static ExceptionHandler exceptionHandler;
    public static void addEventListener(IJEventListener listener) {
        eventListeners.addElement(listener);
    }
    
    public static void removeEventListener(IJEventListener listener) {
        eventListeners.removeElement(listener);
    }
    
    public static void notifyEventListeners(int eventID) {
        synchronized (eventListeners) {
            for (int i=0; i<eventListeners.size(); i++) {
                IJEventListener listener = (IJEventListener)eventListeners.elementAt(i);
                listener.eventOccurred(eventID);
            }
        }
    }
    
    public static void setProperty(String key, Object value) {
        if (properties==null)
            properties = new Properties();
        if (value==null)
            properties.remove(key);
        else
            properties.put(key, value);
    }
    
    public static Object getProperty(String key) {
        if (properties==null)
            return null;
        else
            return properties.get(key);
    }
    
    public static boolean statusBarProtected() {
        return protectStatusBar;
    }
    
    public static void protectStatusBar(boolean protect) {
        protectStatusBar = protect;
        if (!protectStatusBar)
            statusBarThread = null;
    }
}