/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org Copyright (c) 2004-09 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package processing.core; import java.applet.*; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.io.*; import java.lang.reflect.*; import java.net.*; import java.text.*; import java.util.*; import java.util.regex.*; import java.util.zip.*; import javax.imageio.ImageIO; import javax.swing.JFileChooser; import javax.swing.SwingUtilities; /** * Base class for all sketches that use processing.core. *
* Note that you should not use AWT or Swing components inside a Processing * applet. The surface is made to automatically update itself, and will cause * problems with redraw of components drawn above it. If you'd like to * integrate other Java components, see below. * * As of release 0145, Processing uses active mode rendering in all cases. * All animation tasks happen on the "Processing Animation Thread". The * setup() and draw() methods are handled by that thread, and events (like * mouse movement and key presses, which are fired by the event dispatch * thread or EDT) are queued to be (safely) handled at the end of draw(). * For code that needs to run on the EDT, use SwingUtilities.invokeLater(). * When doing so, be careful to synchronize between that code (since * invokeLater() will make your code run from the EDT) and the Processing * animation thread. Use of a callback function or the registerXxx() methods * in PApplet can help ensure that your code doesn't do something naughty. * * As of release 0136 of Processing, we have discontinued support for versions * of Java prior to 1.5. We don't have enough people to support it, and for a * project of our size, we should be focusing on the future, rather than * working around legacy Java code. In addition, Java 1.5 gives us access to * better timing facilities which will improve the steadiness of animation. * * This class extends Applet instead of JApplet because 1) historically, * we supported Java 1.1, which does not include Swing (without an * additional, sizable, download), and 2) Swing is a bloated piece of crap. * A Processing applet is a heavyweight AWT component, and can be used the * same as any other AWT component, with or without Swing. * * Similarly, Processing runs in a Frame and not a JFrame. However, there's * nothing to prevent you from embedding a PApplet into a JFrame, it's just * that the base version uses a regular AWT frame because there's simply * no need for swing in that context. If people want to use Swing, they can * embed themselves as they wish. * * It is possible to use PApplet, along with core.jar in other projects. * In addition to enabling you to use Java 1.5+ features with your sketch, * this also allows you to embed a Processing drawing area into another Java * application. This means you can use standard GUI controls with a Processing * sketch. Because AWT and Swing GUI components cannot be used on top of a * PApplet, you can instead embed the PApplet inside another GUI the way you * would any other Component. * * It is also possible to resize the Processing window by including * frame.setResizable(true) inside your setup() method. * Note that the Java method frame.setSize() will not work unless * you first set the frame to be resizable. * * Because the default animation thread will run at 60 frames per second, * an embedded PApplet can make the parent sluggish. You can use frameRate() * to make it update less often, or you can use noLoop() and loop() to disable * and then re-enable looping. If you want to only update the sketch * intermittently, use noLoop() inside setup(), and redraw() whenever * the screen needs to be updated once (or loop() to re-enable the animation * thread). The following example embeds a sketch and also uses the noLoop() * and redraw() methods. You need not use noLoop() and redraw() when embedding * if you want your application to animate continuously. ** public class ExampleFrame extends Frame { * * public ExampleFrame() { * super("Embedded PApplet"); * * setLayout(new BorderLayout()); * PApplet embed = new Embedded(); * add(embed, BorderLayout.CENTER); * * // important to call this whenever embedding a PApplet. * // It ensures that the animation thread is started and * // that other internal variables are properly set. * embed.init(); * } * } * * public class Embedded extends PApplet { * * public void setup() { * // original setup code here ... * size(400, 400); * * // prevent thread from starving everything else * noLoop(); * } * * public void draw() { * // drawing code goes here * } * * public void mousePressed() { * // do something based on mouse movement * * // update the screen (run draw once) * redraw(); * } * } ** *
I was asked about Processing with multiple displays, and for lack of a * better place to document it, things will go here.
*You can address both screens by making a window the width of both, * and the height of the maximum of both screens. In this case, do not use * present mode, because that's exclusive to one screen. Basically it'll * give you a PApplet that spans both screens. If using one half to control * and the other half for graphics, you'd just have to put the 'live' stuff * on one half of the canvas, the control stuff on the other. This works * better in windows because on the mac we can't get rid of the menu bar * unless it's running in present mode.
*For more control, you need to write straight java code that uses p5. * You can create two windows, that are shown on two separate screens, * that have their own PApplet. this is just one of the tradeoffs of one of * the things that we don't support in p5 from within the environment * itself (we must draw the line somewhere), because of how messy it would * get to start talking about multiple screens. It's also not that tough to * do by hand w/ some Java code.
*/ public class PApplet extends Applet implements PConstants, Runnable, MouseListener, MouseMotionListener, KeyListener, FocusListener { /** * Full name of the Java version (i.e. 1.5.0_11). * Prior to 0125, this was only the first three digits. */ public static final String javaVersionName = System.getProperty("java.version"); /** * Version of Java that's in use, whether 1.1 or 1.3 or whatever, * stored as a float. ** Note that because this is stored as a float, the values may * not be exactly 1.3 or 1.4. Instead, make sure you're * comparing against 1.3f or 1.4f, which will have the same amount * of error (i.e. 1.40000001). This could just be a double, but * since Processing only uses floats, it's safer for this to be a float * because there's no good way to specify a double with the preproc. */ public static final float javaVersion = new Float(javaVersionName.substring(0, 3)).floatValue(); /** * Current platform in use. *
* Equivalent to System.getProperty("os.name"), just used internally. */ /** * Current platform in use, one of the * PConstants WINDOWS, MACOSX, MACOS9, LINUX or OTHER. */ static public int platform; /** * Name associated with the current 'platform' (see PConstants.platformNames) */ //static public String platformName; static { String osname = System.getProperty("os.name"); if (osname.indexOf("Mac") != -1) { platform = MACOSX; } else if (osname.indexOf("Windows") != -1) { platform = WINDOWS; } else if (osname.equals("Linux")) { // true for the ibm vm platform = LINUX; } else { platform = OTHER; } } /** * Modifier flags for the shortcut key used to trigger menus. * (Cmd on Mac OS X, Ctrl on Linux and Windows) */ static public final int MENU_SHORTCUT = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); /** The PGraphics renderer associated with this PApplet */ public PGraphics g; //protected Object glock = new Object(); // for sync /** The frame containing this applet (if any) */ public Frame frame; /** * The screen size when the applet was started. *
* Access this via screen.width and screen.height. To make an applet * run at full screen, use size(screen.width, screen.height). *
* If you have multiple displays, this will be the size of the main * display. Running full screen across multiple displays isn't * particularly supported, and requires more monkeying with the values. * This probably can't/won't be fixed until/unless I get a dual head * system. *
* Note that this won't update if you change the resolution * of your screen once the the applet is running. *
* This variable is not static, because future releases need to be better * at handling multiple displays. */ public Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); /** * A leech graphics object that is echoing all events. */ public PGraphics recorder; /** * Command line options passed in from main(). *
* This does not include the arguments passed in to PApplet itself. */ public String args[]; /** Path to sketch folder */ public String sketchPath; //folder; /** When debugging headaches */ static final boolean THREAD_DEBUG = false; /** Default width and height for applet when not specified */ static public final int DEFAULT_WIDTH = 100; static public final int DEFAULT_HEIGHT = 100; /** * Minimum dimensions for the window holding an applet. * This varies between platforms, Mac OS X 10.3 can do any height * but requires at least 128 pixels width. Windows XP has another * set of limitations. And for all I know, Linux probably lets you * make windows with negative sizes. */ static public final int MIN_WINDOW_WIDTH = 128; static public final int MIN_WINDOW_HEIGHT = 128; /** * Exception thrown when size() is called the first time. *
* This is used internally so that setup() is forced to run twice * when the renderer is changed. This is the only way for us to handle * invoking the new renderer while also in the midst of rendering. */ static public class RendererChangeException extends RuntimeException { } /** * true if no size() command has been executed. This is used to wait until * a size has been set before placing in the window and showing it. */ public boolean defaultSize; volatile boolean resizeRequest; volatile int resizeWidth; volatile int resizeHeight; /** * Pixel buffer from this applet's PGraphics. *
* When used with OpenGL or Java2D, this value will * be null until loadPixels() has been called. */ public int pixels[]; /** width of this applet's associated PGraphics */ public int width; /** height of this applet's associated PGraphics */ public int height; /** current x position of the mouse */ public int mouseX; /** current y position of the mouse */ public int mouseY; /** * Previous x/y position of the mouse. This will be a different value * when inside a mouse handler (like the mouseMoved() method) versus * when inside draw(). Inside draw(), pmouseX is updated once each * frame, but inside mousePressed() and friends, it's updated each time * an event comes through. Be sure to use only one or the other type of * means for tracking pmouseX and pmouseY within your sketch, otherwise * you're gonna run into trouble. */ public int pmouseX, pmouseY; /** * previous mouseX/Y for the draw loop, separated out because this is * separate from the pmouseX/Y when inside the mouse event handlers. */ protected int dmouseX, dmouseY; /** * pmouseX/Y for the event handlers (mousePressed(), mouseDragged() etc) * these are different because mouse events are queued to the end of * draw, so the previous position has to be updated on each event, * as opposed to the pmouseX/Y that's used inside draw, which is expected * to be updated once per trip through draw(). */ protected int emouseX, emouseY; /** * Used to set pmouseX/Y to mouseX/Y the first time mouseX/Y are used, * otherwise pmouseX/Y are always zero, causing a nasty jump. *
* Just using (frameCount == 0) won't work since mouseXxxxx() * may not be called until a couple frames into things. */ public boolean firstMouse; /** * Last mouse button pressed, one of LEFT, CENTER, or RIGHT. *
* If running on Mac OS, a ctrl-click will be interpreted as * the righthand mouse button (unlike Java, which reports it as * the left mouse). */ public int mouseButton; public boolean mousePressed; public MouseEvent mouseEvent; /** * Last key pressed. *
* If it's a coded key, i.e. UP/DOWN/CTRL/SHIFT/ALT, * this will be set to CODED (0xffff or 65535). */ public char key; /** * When "key" is set to CODED, this will contain a Java key code. *
* For the arrow keys, keyCode will be one of UP, DOWN, LEFT and RIGHT. * Also available are ALT, CONTROL and SHIFT. A full set of constants * can be obtained from java.awt.event.KeyEvent, from the VK_XXXX variables. */ public int keyCode; /** * true if the mouse is currently pressed. */ public boolean keyPressed; /** * the last KeyEvent object passed into a mouse function. */ public KeyEvent keyEvent; /** * Gets set to true/false as the applet gains/loses focus. */ public boolean focused = false; /** * true if the applet is online. *
* This can be used to test how the applet should behave * since online situations are different (no file writing, etc). */ public boolean online = false; /** * Time in milliseconds when the applet was started. *
* Used by the millis() function. */ long millisOffset; /** * The current value of frames per second. *
* The initial value will be 10 fps, and will be updated with each * frame thereafter. The value is not instantaneous (since that * wouldn't be very useful since it would jump around so much), * but is instead averaged (integrated) over several frames. * As such, this value won't be valid until after 5-10 frames. */ public float frameRate = 10; /** Last time in nanoseconds that frameRate was checked */ protected long frameRateLastNanos = 0; /** As of release 0116, frameRate(60) is called as a default */ protected float frameRateTarget = 60; protected long frameRatePeriod = 1000000000L / 60L; protected boolean looping; /** flag set to true when a redraw is asked for by the user */ protected boolean redraw; /** * How many frames have been displayed since the applet started. *
* This value is read-only do not attempt to set it, * otherwise bad things will happen. *
* Inside setup(), frameCount is 0. * For the first iteration of draw(), frameCount will equal 1. */ public int frameCount; /** * true if this applet has had it. */ public boolean finished; /** * true if exit() has been called so that things shut down * once the main thread kicks off. */ protected boolean exitCalled; Thread thread; protected RegisteredMethods sizeMethods; protected RegisteredMethods preMethods, drawMethods, postMethods; protected RegisteredMethods mouseEventMethods, keyEventMethods; protected RegisteredMethods disposeMethods; // messages to send if attached as an external vm /** * Position of the upper-lefthand corner of the editor window * that launched this applet. */ static public final String ARGS_EDITOR_LOCATION = "--editor-location"; /** * Location for where to position the applet window on screen. *
* This is used by the editor to when saving the previous applet * location, or could be used by other classes to launch at a * specific position on-screen. */ static public final String ARGS_EXTERNAL = "--external"; static public final String ARGS_LOCATION = "--location"; static public final String ARGS_DISPLAY = "--display"; static public final String ARGS_BGCOLOR = "--bgcolor"; static public final String ARGS_PRESENT = "--present"; static public final String ARGS_EXCLUSIVE = "--exclusive"; static public final String ARGS_STOP_COLOR = "--stop-color"; static public final String ARGS_HIDE_STOP = "--hide-stop"; /** * Allows the user or PdeEditor to set a specific sketch folder path. *
* Used by PdeEditor to pass in the location where saveFrame() * and all that stuff should write things. */ static public final String ARGS_SKETCH_FOLDER = "--sketch-path"; /** * When run externally to a PdeEditor, * this is sent by the applet when it quits. */ //static public final String EXTERNAL_QUIT = "__QUIT__"; static public final String EXTERNAL_STOP = "__STOP__"; /** * When run externally to a PDE Editor, this is sent by the applet * whenever the window is moved. *
* This is used so that the editor can re-open the sketch window * in the same position as the user last left it. */ static public final String EXTERNAL_MOVE = "__MOVE__"; /** true if this sketch is being run by the PDE */ boolean external = false; static final String ERROR_MIN_MAX = "Cannot use min() or max() on an empty array."; // during rev 0100 dev cycle, working on new threading model, // but need to disable and go conservative with changes in order // to get pdf and audio working properly first. // for 0116, the CRUSTY_THREADS are being disabled to fix lots of bugs. //static final boolean CRUSTY_THREADS = false; //true; public void init() { // println("Calling init()"); // send tab keys through to the PApplet setFocusTraversalKeysEnabled(false); millisOffset = System.currentTimeMillis(); finished = false; // just for clarity // this will be cleared by draw() if it is not overridden looping = true; redraw = true; // draw this guy once firstMouse = true; // these need to be inited before setup sizeMethods = new RegisteredMethods(); preMethods = new RegisteredMethods(); drawMethods = new RegisteredMethods(); postMethods = new RegisteredMethods(); mouseEventMethods = new RegisteredMethods(); keyEventMethods = new RegisteredMethods(); disposeMethods = new RegisteredMethods(); try { getAppletContext(); online = true; } catch (NullPointerException e) { online = false; } try { if (sketchPath == null) { sketchPath = System.getProperty("user.dir"); } } catch (Exception e) { } // may be a security problem Dimension size = getSize(); if ((size.width != 0) && (size.height != 0)) { // When this PApplet is embedded inside a Java application with other // Component objects, its size() may already be set externally (perhaps // by a LayoutManager). In this case, honor that size as the default. // Size of the component is set, just create a renderer. g = makeGraphics(size.width, size.height, getSketchRenderer(), null, true); // This doesn't call setSize() or setPreferredSize() because the fact // that a size was already set means that someone is already doing it. } else { // Set the default size, until the user specifies otherwise this.defaultSize = true; int w = getSketchWidth(); int h = getSketchHeight(); g = makeGraphics(w, h, getSketchRenderer(), null, true); // Fire component resize event setSize(w, h); setPreferredSize(new Dimension(w, h)); } width = g.width; height = g.height; addListeners(); // this is automatically called in applets // though it's here for applications anyway start(); } public int getSketchWidth() { return DEFAULT_WIDTH; } public int getSketchHeight() { return DEFAULT_HEIGHT; } public String getSketchRenderer() { return JAVA2D; } /** * Called by the browser or applet viewer to inform this applet that it * should start its execution. It is called after the init method and * each time the applet is revisited in a Web page. *
* Called explicitly via the first call to PApplet.paint(), because * PAppletGL needs to have a usable screen before getting things rolling. */ public void start() { // When running inside a browser, start() will be called when someone // returns to a page containing this applet. // http://dev.processing.org/bugs/show_bug.cgi?id=581 finished = false; if (thread != null) return; thread = new Thread(this, "Animation Thread"); thread.start(); } /** * Called by the browser or applet viewer to inform * this applet that it should stop its execution. * * Unfortunately, there are no guarantees from the Java spec * when or if stop() will be called (i.e. on browser quit, * or when moving between web pages), and it's not always called. */ public void stop() { // bringing this back for 0111, hoping it'll help opengl shutdown finished = true; // why did i comment this out? // don't run stop and disposers twice if (thread == null) return; thread = null; // call to shut down renderer, in case it needs it (pdf does) if (g != null) g.dispose(); // maybe this should be done earlier? might help ensure it gets called // before the vm just craps out since 1.5 craps out so aggressively. disposeMethods.handle(); } /** * Called by the browser or applet viewer to inform this applet * that it is being reclaimed and that it should destroy * any resources that it has allocated. * * This also attempts to call PApplet.stop(), in case there * was an inadvertent override of the stop() function by a user. * * destroy() supposedly gets called as the applet viewer * is shutting down the applet. stop() is called * first, and then destroy() to really get rid of things. * no guarantees on when they're run (on browser quit, or * when moving between pages), though. */ public void destroy() { ((PApplet)this).stop(); } /** * This returns the last width and height specified by the user * via the size() command. */ // public Dimension getPreferredSize() { // return new Dimension(width, height); // } // public void addNotify() { // super.addNotify(); // println("addNotify()"); // } ////////////////////////////////////////////////////////////// public class RegisteredMethods { int count; Object objects[]; Method methods[]; // convenience version for no args public void handle() { handle(new Object[] { }); } public void handle(Object oargs[]) { for (int i = 0; i < count; i++) { try { //System.out.println(objects[i] + " " + args); methods[i].invoke(objects[i], oargs); } catch (Exception e) { e.printStackTrace(); } } } public void add(Object object, Method method) { if (objects == null) { objects = new Object[5]; methods = new Method[5]; } if (count == objects.length) { objects = (Object[]) PApplet.expand(objects); methods = (Method[]) PApplet.expand(methods); // Object otemp[] = new Object[count << 1]; // System.arraycopy(objects, 0, otemp, 0, count); // objects = otemp; // Method mtemp[] = new Method[count << 1]; // System.arraycopy(methods, 0, mtemp, 0, count); // methods = mtemp; } objects[count] = object; methods[count] = method; count++; } /** * Removes first object/method pair matched (and only the first, * must be called multiple times if object is registered multiple times). * Does not shrink array afterwards, silently returns if method not found. */ public void remove(Object object, Method method) { int index = findIndex(object, method); if (index != -1) { // shift remaining methods by one to preserve ordering count--; for (int i = index; i < count; i++) { objects[i] = objects[i+1]; methods[i] = methods[i+1]; } // clean things out for the gc's sake objects[count] = null; methods[count] = null; } } protected int findIndex(Object object, Method method) { for (int i = 0; i < count; i++) { if (objects[i] == object && methods[i].equals(method)) { //objects[i].equals() might be overridden, so use == for safety // since here we do care about actual object identity //methods[i]==method is never true even for same method, so must use // equals(), this should be safe because of object identity return i; } } return -1; } } public void registerSize(Object o) { Class> methodArgs[] = new Class[] { Integer.TYPE, Integer.TYPE }; registerWithArgs(sizeMethods, "size", o, methodArgs); } public void registerPre(Object o) { registerNoArgs(preMethods, "pre", o); } public void registerDraw(Object o) { registerNoArgs(drawMethods, "draw", o); } public void registerPost(Object o) { registerNoArgs(postMethods, "post", o); } public void registerMouseEvent(Object o) { Class> methodArgs[] = new Class[] { MouseEvent.class }; registerWithArgs(mouseEventMethods, "mouseEvent", o, methodArgs); } public void registerKeyEvent(Object o) { Class> methodArgs[] = new Class[] { KeyEvent.class }; registerWithArgs(keyEventMethods, "keyEvent", o, methodArgs); } public void registerDispose(Object o) { registerNoArgs(disposeMethods, "dispose", o); } protected void registerNoArgs(RegisteredMethods meth, String name, Object o) { Class> c = o.getClass(); try { Method method = c.getMethod(name, new Class[] {}); meth.add(o, method); } catch (NoSuchMethodException nsme) { die("There is no " + name + "() method in the class " + o.getClass().getName()); } catch (Exception e) { die("Could not register " + name + " + () for " + o, e); } } protected void registerWithArgs(RegisteredMethods meth, String name, Object o, Class> cargs[]) { Class> c = o.getClass(); try { Method method = c.getMethod(name, cargs); meth.add(o, method); } catch (NoSuchMethodException nsme) { die("There is no " + name + "() method in the class " + o.getClass().getName()); } catch (Exception e) { die("Could not register " + name + " + () for " + o, e); } } public void unregisterSize(Object o) { Class> methodArgs[] = new Class[] { Integer.TYPE, Integer.TYPE }; unregisterWithArgs(sizeMethods, "size", o, methodArgs); } public void unregisterPre(Object o) { unregisterNoArgs(preMethods, "pre", o); } public void unregisterDraw(Object o) { unregisterNoArgs(drawMethods, "draw", o); } public void unregisterPost(Object o) { unregisterNoArgs(postMethods, "post", o); } public void unregisterMouseEvent(Object o) { Class> methodArgs[] = new Class[] { MouseEvent.class }; unregisterWithArgs(mouseEventMethods, "mouseEvent", o, methodArgs); } public void unregisterKeyEvent(Object o) { Class> methodArgs[] = new Class[] { KeyEvent.class }; unregisterWithArgs(keyEventMethods, "keyEvent", o, methodArgs); } public void unregisterDispose(Object o) { unregisterNoArgs(disposeMethods, "dispose", o); } protected void unregisterNoArgs(RegisteredMethods meth, String name, Object o) { Class> c = o.getClass(); try { Method method = c.getMethod(name, new Class[] {}); meth.remove(o, method); } catch (Exception e) { die("Could not unregister " + name + "() for " + o, e); } } protected void unregisterWithArgs(RegisteredMethods meth, String name, Object o, Class> cargs[]) { Class> c = o.getClass(); try { Method method = c.getMethod(name, cargs); meth.remove(o, method); } catch (Exception e) { die("Could not unregister " + name + "() for " + o, e); } } ////////////////////////////////////////////////////////////// public void setup() { } public void draw() { // if no draw method, then shut things down //System.out.println("no draw method, goodbye"); finished = true; } ////////////////////////////////////////////////////////////// protected void resizeRenderer(int iwidth, int iheight) { // println("resizeRenderer request for " + iwidth + " " + iheight); if (width != iwidth || height != iheight) { // println(" former size was " + width + " " + height); g.setSize(iwidth, iheight); width = iwidth; height = iheight; } } /** * Starts up and creates a two-dimensional drawing surface, * or resizes the current drawing surface. ** This should be the first thing called inside of setup(). *
* If using Java 1.3 or later, this will default to using * PGraphics2, the Java2D-based renderer. If using Java 1.1, * or if PGraphics2 is not available, then PGraphics will be used. * To set your own renderer, use the other version of the size() * method that takes a renderer as its last parameter. *
* If called once a renderer has already been set, this will * use the previous renderer and simply resize it. */ public void size(int iwidth, int iheight) { size(iwidth, iheight, JAVA2D, null); } public void size(int iwidth, int iheight, String irenderer) { size(iwidth, iheight, irenderer, null); } /** * Creates a new PGraphics object and sets it to the specified size. * * Note that you cannot change the renderer once outside of setup(). * In most cases, you can call size() to give it a new size, * but you need to always ask for the same renderer, otherwise * you're gonna run into trouble. * * The size() method should *only* be called from inside the setup() or * draw() methods, so that it is properly run on the main animation thread. * To change the size of a PApplet externally, use setSize(), which will * update the component size, and queue a resize of the renderer as well. */ public void size(final int iwidth, final int iheight, String irenderer, String ipath) { // Run this from the EDT, just cuz it's AWT stuff (or maybe later Swing) SwingUtilities.invokeLater(new Runnable() { public void run() { // Set the preferred size so that the layout managers can handle it setPreferredSize(new Dimension(iwidth, iheight)); setSize(iwidth, iheight); } }); // ensure that this is an absolute path if (ipath != null) ipath = savePath(ipath); String currentRenderer = g.getClass().getName(); if (currentRenderer.equals(irenderer)) { // Avoid infinite loop of throwing exception to reset renderer resizeRenderer(iwidth, iheight); //redraw(); // will only be called insize draw() } else { // renderer is being changed // otherwise ok to fall through and create renderer below // the renderer is changing, so need to create a new object g = makeGraphics(iwidth, iheight, irenderer, ipath, true); width = iwidth; height = iheight; // fire resize event to make sure the applet is the proper size // setSize(iwidth, iheight); // this is the function that will run if the user does their own // size() command inside setup, so set defaultSize to false. defaultSize = false; // throw an exception so that setup() is called again // but with a properly sized render // this is for opengl, which needs a valid, properly sized // display before calling anything inside setup(). throw new RendererChangeException(); } } /** * Create an offscreen PGraphics object for drawing. This can be used * for bitmap or vector images drawing or rendering. *
* * PGraphics big; * * void setup() { * big = createGraphics(3000, 3000, P3D); * * big.beginDraw(); * big.background(128); * big.line(20, 1800, 1800, 900); * // etc.. * big.endDraw(); * * // make sure the file is written to the sketch folder * big.save("big.tif"); * } * **
* Examples for key handling: * (Tested on Windows XP, please notify if different on other * platforms, I have a feeling Mac OS and Linux may do otherwise) *
* 1. Pressing 'a' on the keyboard: * keyPressed with key == 'a' and keyCode == 'A' * keyTyped with key == 'a' and keyCode == 0 * keyReleased with key == 'a' and keyCode == 'A' * * 2. Pressing 'A' on the keyboard: * keyPressed with key == 'A' and keyCode == 'A' * keyTyped with key == 'A' and keyCode == 0 * keyReleased with key == 'A' and keyCode == 'A' * * 3. Pressing 'shift', then 'a' on the keyboard (caps lock is off): * keyPressed with key == CODED and keyCode == SHIFT * keyPressed with key == 'A' and keyCode == 'A' * keyTyped with key == 'A' and keyCode == 0 * keyReleased with key == 'A' and keyCode == 'A' * keyReleased with key == CODED and keyCode == SHIFT * * 4. Holding down the 'a' key. * The following will happen several times, * depending on your machine's "key repeat rate" settings: * keyPressed with key == 'a' and keyCode == 'A' * keyTyped with key == 'a' and keyCode == 0 * When you finally let go, you'll get: * keyReleased with key == 'a' and keyCode == 'A' * * 5. Pressing and releasing the 'shift' key * keyPressed with key == CODED and keyCode == SHIFT * keyReleased with key == CODED and keyCode == SHIFT * (note there is no keyTyped) * * 6. Pressing the tab key in an applet with Java 1.4 will * normally do nothing, but PApplet dynamically shuts * this behavior off if Java 1.4 is in use (tested 1.4.2_05 Windows). * Java 1.1 (Microsoft VM) passes the TAB key through normally. * Not tested on other platforms or for 1.3. **/ public void keyPressed() { } /** * See keyPressed(). */ public void keyReleased() { } /** * Only called for "regular" keys like letters, * see keyPressed() for full documentation. */ public void keyTyped() { } ////////////////////////////////////////////////////////////// // i am focused man, and i'm not afraid of death. // and i'm going all out. i circle the vultures in a van // and i run the block. public void focusGained() { } public void focusGained(FocusEvent e) { focused = true; focusGained(); } public void focusLost() { } public void focusLost(FocusEvent e) { focused = false; focusLost(); } ////////////////////////////////////////////////////////////// // getting the time /** * Get the number of milliseconds since the applet started. *
* This is a function, rather than a variable, because it may * change multiple times per frame. */ public int millis() { return (int) (System.currentTimeMillis() - millisOffset); } /** Seconds position of the current time. */ static public int second() { return Calendar.getInstance().get(Calendar.SECOND); } /** Minutes position of the current time. */ static public int minute() { return Calendar.getInstance().get(Calendar.MINUTE); } /** * Hour position of the current time in international format (0-23). *
* To convert this value to American time:
*
int yankeeHour = (hour() % 12); * if (yankeeHour == 0) yankeeHour = 12;*/ static public int hour() { return Calendar.getInstance().get(Calendar.HOUR_OF_DAY); } /** * Get the current day of the month (1 through 31). *
* If you're looking for the day of the week (M-F or whatever)
* or day of the year (1..365) then use java's Calendar.get()
*/
static public int day() {
return Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
}
/**
* Get the current month in range 1 through 12.
*/
static public int month() {
// months are number 0..11 so change to colloquial 1..12
return Calendar.getInstance().get(Calendar.MONTH) + 1;
}
/**
* Get the current year.
*/
static public int year() {
return Calendar.getInstance().get(Calendar.YEAR);
}
//////////////////////////////////////////////////////////////
// controlling time (playing god)
/**
* The delay() function causes the program to halt for a specified time.
* Delay times are specified in thousandths of a second. For example,
* running delay(3000) will stop the program for three seconds and
* delay(500) will stop the program for a half-second. Remember: the
* display window is updated only at the end of draw(), so putting more
* than one delay() inside draw() will simply add them together and the new
* frame will be drawn when the total delay is over.
*
* I'm not sure if this is even helpful anymore, as the screen isn't
* updated before or after the delay, meaning which means it just
* makes the app lock up temporarily.
*/
public void delay(int napTime) {
if (frameCount != 0) {
if (napTime > 0) {
try {
Thread.sleep(napTime);
} catch (InterruptedException e) { }
}
}
}
/**
* Set a target frameRate. This will cause delay() to be called
* after each frame so that the sketch synchronizes to a particular speed.
* Note that this only sets the maximum frame rate, it cannot be used to
* make a slow sketch go faster. Sketches have no default frame rate
* setting, and will attempt to use maximum processor power to achieve
* maximum speed.
*/
public void frameRate(float newRateTarget) {
frameRateTarget = newRateTarget;
frameRatePeriod = (long) (1000000000.0 / frameRateTarget);
}
//////////////////////////////////////////////////////////////
/**
* Get a param from the web page, or (eventually)
* from a properties file.
*/
public String param(String what) {
if (online) {
return getParameter(what);
} else {
System.err.println("param() only works inside a web browser");
}
return null;
}
/**
* Show status in the status bar of a web browser, or in the
* System.out console. Eventually this might show status in the
* p5 environment itself, rather than relying on the console.
*/
public void status(String what) {
if (online) {
showStatus(what);
} else {
System.out.println(what); // something more interesting?
}
}
public void link(String here) {
link(here, null);
}
/**
* Link to an external page without all the muss.
*
* When run with an applet, uses the browser to open the url, * for applications, attempts to launch a browser with the url. *
* Works on Mac OS X and Windows. For Linux, use: *
open(new String[] { "firefox", url });* or whatever you want as your browser, since Linux doesn't * yet have a standard method for launching URLs. */ public void link(String url, String frameTitle) { if (online) { try { if (frameTitle == null) { getAppletContext().showDocument(new URL(url)); } else { getAppletContext().showDocument(new URL(url), frameTitle); } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Could not open " + url); } } else { try { if (platform == WINDOWS) { // the following uses a shell execute to launch the .html file // note that under cygwin, the .html files have to be chmodded +x // after they're unpacked from the zip file. i don't know why, // and don't understand what this does in terms of windows // permissions. without the chmod, the command prompt says // "Access is denied" in both cygwin and the "dos" prompt. //Runtime.getRuntime().exec("cmd /c " + currentDir + "\\reference\\" + // referenceFile + ".html"); // replace ampersands with control sequence for DOS. // solution contributed by toxi on the bugs board. url = url.replaceAll("&","^&"); // open dos prompt, give it 'start' command, which will // open the url properly. start by itself won't work since // it appears to need cmd Runtime.getRuntime().exec("cmd /c start " + url); } else if (platform == MACOSX) { //com.apple.mrj.MRJFileUtils.openURL(url); try { Class> mrjFileUtils = Class.forName("com.apple.mrj.MRJFileUtils"); Method openMethod = mrjFileUtils.getMethod("openURL", new Class[] { String.class }); openMethod.invoke(null, new Object[] { url }); } catch (Exception e) { e.printStackTrace(); } } else { //throw new RuntimeException("Can't open URLs for this platform"); // Just pass it off to open() and hope for the best open(url); } } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("Could not open " + url); } } } /** * Attempt to open a file using the platform's shell. */ static public void open(String filename) { open(new String[] { filename }); } static String openLauncher; /** * Launch a process using a platforms shell. This version uses an array * to make it easier to deal with spaces in the individual elements. * (This avoids the situation of trying to put single or double quotes * around different bits). */ static public Process open(String argv[]) { String[] params = null; if (platform == WINDOWS) { // just launching the .html file via the shell works // but make sure to chmod +x the .html files first // also place quotes around it in case there's a space // in the user.dir part of the url params = new String[] { "cmd", "/c" }; } else if (platform == MACOSX) { params = new String[] { "open" }; } else if (platform == LINUX) { if (openLauncher == null) { // Attempt to use gnome-open try { Process p = Runtime.getRuntime().exec(new String[] { "gnome-open" }); /*int result =*/ p.waitFor(); // Not installed will throw an IOException (JDK 1.4.2, Ubuntu 7.04) openLauncher = "gnome-open"; } catch (Exception e) { } } if (openLauncher == null) { // Attempt with kde-open try { Process p = Runtime.getRuntime().exec(new String[] { "kde-open" }); /*int result =*/ p.waitFor(); openLauncher = "kde-open"; } catch (Exception e) { } } if (openLauncher == null) { System.err.println("Could not find gnome-open or kde-open, " + "the open() command may not work."); } if (openLauncher != null) { params = new String[] { openLauncher }; } //} else { // give up and just pass it to Runtime.exec() //open(new String[] { filename }); //params = new String[] { filename }; } if (params != null) { // If the 'open', 'gnome-open' or 'cmd' are already included if (params[0].equals(argv[0])) { // then don't prepend those params again return exec(argv); } else { params = concat(params, argv); return exec(params); } } else { return exec(argv); } } static public Process exec(String[] argv) { try { return Runtime.getRuntime().exec(argv); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Could not open " + join(argv, ' ')); } } ////////////////////////////////////////////////////////////// /** * Function for an applet/application to kill itself and * display an error. Mostly this is here to be improved later. */ public void die(String what) { stop(); throw new RuntimeException(what); } /** * Same as above but with an exception. Also needs work. */ public void die(String what, Exception e) { if (e != null) e.printStackTrace(); die(what); } /** * Call to safely exit the sketch when finished. For instance, * to render a single frame, save it, and quit. */ public void exit() { if (thread == null) { // exit immediately, stop() has already been called, // meaning that the main thread has long since exited exit2(); } else if (looping) { // stop() will be called as the thread exits finished = true; // tell the code to call exit2() to do a System.exit() // once the next draw() has completed exitCalled = true; } else if (!looping) { // if not looping, need to call stop explicitly, // because the main thread will be sleeping stop(); // now get out exit2(); } } void exit2() { try { System.exit(0); } catch (SecurityException e) { // don't care about applet security exceptions } } ////////////////////////////////////////////////////////////// public void method(String name) { // final Object o = this; // final Class> c = getClass(); try { Method method = getClass().getMethod(name, new Class[] {}); method.invoke(this, new Object[] { }); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.getTargetException().printStackTrace(); } catch (NoSuchMethodException nsme) { System.err.println("There is no " + name + "() method " + "in the class " + getClass().getName()); } catch (Exception e) { e.printStackTrace(); } } public void thread(final String name) { Thread later = new Thread() { public void run() { method(name); } }; later.start(); } /* public void thread(String name) { final Object o = this; final Class> c = getClass(); try { final Method method = c.getMethod(name, new Class[] {}); Thread later = new Thread() { public void run() { try { method.invoke(o, new Object[] { }); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.getTargetException().printStackTrace(); } } }; later.start(); } catch (NoSuchMethodException nsme) { System.err.println("There is no " + name + "() method " + "in the class " + getClass().getName()); } catch (Exception e) { e.printStackTrace(); } } */ ////////////////////////////////////////////////////////////// // SCREEN GRABASS /** * Intercepts any relative paths to make them absolute (relative * to the sketch folder) before passing to save() in PImage. * (Changed in 0100) */ public void save(String filename) { g.save(savePath(filename)); } /** * Grab an image of what's currently in the drawing area and save it * as a .tif or .tga file. *
* Best used just before endDraw() at the end of your draw(). * This can only create .tif or .tga images, so if neither extension * is specified it defaults to writing a tiff and adds a .tif suffix. */ public void saveFrame() { try { g.save(savePath("screen-" + nf(frameCount, 4) + ".tif")); } catch (SecurityException se) { System.err.println("Can't use saveFrame() when running in a browser, " + "unless using a signed applet."); } } /** * Save the current frame as a .tif or .tga image. *
* The String passed in can contain a series of # signs * that will be replaced with the screengrab number. *
* i.e. saveFrame("blah-####.tif"); * // saves a numbered tiff image, replacing the * // #### signs with zeros and the frame number*/ public void saveFrame(String what) { try { g.save(savePath(insertFrame(what))); } catch (SecurityException se) { System.err.println("Can't use saveFrame() when running in a browser, " + "unless using a signed applet."); } } /** * Check a string for #### signs to see if the frame number should be * inserted. Used for functions like saveFrame() and beginRecord() to * replace the # marks with the frame number. If only one # is used, * it will be ignored, under the assumption that it's probably not * intended to be the frame number. */ protected String insertFrame(String what) { int first = what.indexOf('#'); int last = what.lastIndexOf('#'); if ((first != -1) && (last - first > 0)) { String prefix = what.substring(0, first); int count = last - first + 1; String suffix = what.substring(last + 1); return prefix + nf(frameCount, count) + suffix; } return what; // no change } ////////////////////////////////////////////////////////////// // CURSOR // int cursorType = ARROW; // cursor type boolean cursorVisible = true; // cursor visibility flag PImage invisibleCursor; /** * Set the cursor type */ public void cursor(int cursorType) { setCursor(Cursor.getPredefinedCursor(cursorType)); cursorVisible = true; this.cursorType = cursorType; } /** * Replace the cursor with the specified PImage. The x- and y- * coordinate of the center will be the center of the image. */ public void cursor(PImage image) { cursor(image, image.width/2, image.height/2); } /** * Set a custom cursor to an image with a specific hotspot. * Only works with JDK 1.2 and later. * Currently seems to be broken on Java 1.4 for Mac OS X *
* Based on code contributed by Amit Pitaru, plus additional * code to handle Java versions via reflection by Jonathan Feinberg. * Reflection removed for release 0128 and later. */ public void cursor(PImage image, int hotspotX, int hotspotY) { // don't set this as cursor type, instead use cursor_type // to save the last cursor used in case cursor() is called //cursor_type = Cursor.CUSTOM_CURSOR; Image jimage = createImage(new MemoryImageSource(image.width, image.height, image.pixels, 0, image.width)); Point hotspot = new Point(hotspotX, hotspotY); Toolkit tk = Toolkit.getDefaultToolkit(); Cursor cursor = tk.createCustomCursor(jimage, hotspot, "Custom Cursor"); setCursor(cursor); cursorVisible = true; } /** * Show the cursor after noCursor() was called. * Notice that the program remembers the last set cursor type */ public void cursor() { // maybe should always set here? seems dangerous, since // it's likely that java will set the cursor to something // else on its own, and the applet will be stuck b/c bagel // thinks that the cursor is set to one particular thing if (!cursorVisible) { cursorVisible = true; setCursor(Cursor.getPredefinedCursor(cursorType)); } } /** * Hide the cursor by creating a transparent image * and using it as a custom cursor. */ public void noCursor() { if (!cursorVisible) return; // don't hide if already hidden. if (invisibleCursor == null) { invisibleCursor = new PImage(16, 16, ARGB); } // was formerly 16x16, but the 0x0 was added by jdf as a fix // for macosx, which wasn't honoring the invisible cursor cursor(invisibleCursor, 8, 8); cursorVisible = false; } ////////////////////////////////////////////////////////////// static public void print(byte what) { System.out.print(what); System.out.flush(); } static public void print(boolean what) { System.out.print(what); System.out.flush(); } static public void print(char what) { System.out.print(what); System.out.flush(); } static public void print(int what) { System.out.print(what); System.out.flush(); } static public void print(float what) { System.out.print(what); System.out.flush(); } static public void print(String what) { System.out.print(what); System.out.flush(); } static public void print(Object what) { if (what == null) { // special case since this does fuggly things on > 1.1 System.out.print("null"); } else { System.out.println(what.toString()); } } // static public void println() { System.out.println(); } // static public void println(byte what) { print(what); System.out.println(); } static public void println(boolean what) { print(what); System.out.println(); } static public void println(char what) { print(what); System.out.println(); } static public void println(int what) { print(what); System.out.println(); } static public void println(float what) { print(what); System.out.println(); } static public void println(String what) { print(what); System.out.println(); } static public void println(Object what) { if (what == null) { // special case since this does fuggly things on > 1.1 System.out.println("null"); } else { String name = what.getClass().getName(); if (name.charAt(0) == '[') { switch (name.charAt(1)) { case '[': // don't even mess with multi-dimensional arrays (case '[') // or anything else that's not int, float, boolean, char System.out.println(what); break; case 'L': // print a 1D array of objects as individual elements Object poo[] = (Object[]) what; for (int i = 0; i < poo.length; i++) { if (poo[i] instanceof String) { System.out.println("[" + i + "] \"" + poo[i] + "\""); } else { System.out.println("[" + i + "] " + poo[i]); } } break; case 'Z': // boolean boolean zz[] = (boolean[]) what; for (int i = 0; i < zz.length; i++) { System.out.println("[" + i + "] " + zz[i]); } break; case 'B': // byte byte bb[] = (byte[]) what; for (int i = 0; i < bb.length; i++) { System.out.println("[" + i + "] " + bb[i]); } break; case 'C': // char char cc[] = (char[]) what; for (int i = 0; i < cc.length; i++) { System.out.println("[" + i + "] '" + cc[i] + "'"); } break; case 'I': // int int ii[] = (int[]) what; for (int i = 0; i < ii.length; i++) { System.out.println("[" + i + "] " + ii[i]); } break; case 'F': // float float ff[] = (float[]) what; for (int i = 0; i < ff.length; i++) { System.out.println("[" + i + "] " + ff[i]); } break; /* case 'D': // double double dd[] = (double[]) what; for (int i = 0; i < dd.length; i++) { System.out.println("[" + i + "] " + dd[i]); } break; */ default: System.out.println(what); } } else { // not an array System.out.println(what); } } } // /* // not very useful, because it only works for public (and protected?) // fields of a class, not local variables to methods public void printvar(String name) { try { Field field = getClass().getDeclaredField(name); println(name + " = " + field.get(this)); } catch (Exception e) { e.printStackTrace(); } } */ ////////////////////////////////////////////////////////////// // MATH // lots of convenience methods for math with floats. // doubles are overkill for processing applets, and casting // things all the time is annoying, thus the functions below. static public final float abs(float n) { return (n < 0) ? -n : n; } static public final int abs(int n) { return (n < 0) ? -n : n; } static public final float sq(float a) { return a*a; } static public final float sqrt(float a) { return (float)Math.sqrt(a); } static public final float log(float a) { return (float)Math.log(a); } static public final float exp(float a) { return (float)Math.exp(a); } static public final float pow(float a, float b) { return (float)Math.pow(a, b); } static public final int max(int a, int b) { return (a > b) ? a : b; } static public final float max(float a, float b) { return (a > b) ? a : b; } static public final int max(int a, int b, int c) { return (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c); } static public final float max(float a, float b, float c) { return (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c); } /** * Find the maximum value in an array. * Throws an ArrayIndexOutOfBoundsException if the array is length 0. * @param list the source array * @return The maximum value */ static public final int max(int[] list) { if (list.length == 0) { throw new ArrayIndexOutOfBoundsException(ERROR_MIN_MAX); } int max = list[0]; for (int i = 1; i < list.length; i++) { if (list[i] > max) max = list[i]; } return max; } /** * Find the maximum value in an array. * Throws an ArrayIndexOutOfBoundsException if the array is length 0. * @param list the source array * @return The maximum value */ static public final float max(float[] list) { if (list.length == 0) { throw new ArrayIndexOutOfBoundsException(ERROR_MIN_MAX); } float max = list[0]; for (int i = 1; i < list.length; i++) { if (list[i] > max) max = list[i]; } return max; } static public final int min(int a, int b) { return (a < b) ? a : b; } static public final float min(float a, float b) { return (a < b) ? a : b; } static public final int min(int a, int b, int c) { return (a < b) ? ((a < c) ? a : c) : ((b < c) ? b : c); } static public final float min(float a, float b, float c) { return (a < b) ? ((a < c) ? a : c) : ((b < c) ? b : c); } /** * Find the minimum value in an array. * Throws an ArrayIndexOutOfBoundsException if the array is length 0. * @param list the source array * @return The minimum value */ static public final int min(int[] list) { if (list.length == 0) { throw new ArrayIndexOutOfBoundsException(ERROR_MIN_MAX); } int min = list[0]; for (int i = 1; i < list.length; i++) { if (list[i] < min) min = list[i]; } return min; } /** * Find the minimum value in an array. * Throws an ArrayIndexOutOfBoundsException if the array is length 0. * @param list the source array * @return The minimum value */ static public final float min(float[] list) { if (list.length == 0) { throw new ArrayIndexOutOfBoundsException(ERROR_MIN_MAX); } float min = list[0]; for (int i = 1; i < list.length; i++) { if (list[i] < min) min = list[i]; } return min; } static public final int constrain(int amt, int low, int high) { return (amt < low) ? low : ((amt > high) ? high : amt); } static public final float constrain(float amt, float low, float high) { return (amt < low) ? low : ((amt > high) ? high : amt); } static public final float sin(float angle) { return (float)Math.sin(angle); } static public final float cos(float angle) { return (float)Math.cos(angle); } static public final float tan(float angle) { return (float)Math.tan(angle); } static public final float asin(float value) { return (float)Math.asin(value); } static public final float acos(float value) { return (float)Math.acos(value); } static public final float atan(float value) { return (float)Math.atan(value); } static public final float atan2(float a, float b) { return (float)Math.atan2(a, b); } static public final float degrees(float radians) { return radians * RAD_TO_DEG; } static public final float radians(float degrees) { return degrees * DEG_TO_RAD; } static public final int ceil(float what) { return (int) Math.ceil(what); } static public final int floor(float what) { return (int) Math.floor(what); } static public final int round(float what) { return (int) Math.round(what); } static public final float mag(float a, float b) { return (float)Math.sqrt(a*a + b*b); } static public final float mag(float a, float b, float c) { return (float)Math.sqrt(a*a + b*b + c*c); } static public final float dist(float x1, float y1, float x2, float y2) { return sqrt(sq(x2-x1) + sq(y2-y1)); } static public final float dist(float x1, float y1, float z1, float x2, float y2, float z2) { return sqrt(sq(x2-x1) + sq(y2-y1) + sq(z2-z1)); } static public final float lerp(float start, float stop, float amt) { return start + (stop-start) * amt; } /** * Normalize a value to exist between 0 and 1 (inclusive). * Mathematically the opposite of lerp(), figures out what proportion * a particular value is relative to start and stop coordinates. */ static public final float norm(float value, float start, float stop) { return (value - start) / (stop - start); } /** * Convenience function to map a variable from one coordinate space * to another. Equivalent to unlerp() followed by lerp(). */ static public final float map(float value, float istart, float istop, float ostart, float ostop) { return ostart + (ostop - ostart) * ((value - istart) / (istop - istart)); } static public final double map(double value, double istart, double istop, double ostart, double ostop) { return ostart + (ostop - ostart) * ((value - istart) / (istop - istart)); } ////////////////////////////////////////////////////////////// // RANDOM NUMBERS Random internalRandom; /** * Return a random number in the range [0, howbig). *
* The number returned will range from zero up to * (but not including) 'howbig'. */ public final float random(float howbig) { // for some reason (rounding error?) Math.random() * 3 // can sometimes return '3' (once in ~30 million tries) // so a check was added to avoid the inclusion of 'howbig' // avoid an infinite loop if (howbig == 0) return 0; // internal random number object if (internalRandom == null) internalRandom = new Random(); float value = 0; do { //value = (float)Math.random() * howbig; value = internalRandom.nextFloat() * howbig; } while (value == howbig); return value; } /** * Return a random number in the range [howsmall, howbig). *
* The number returned will range from 'howsmall' up to * (but not including 'howbig'. *
* If howsmall is >= howbig, howsmall will be returned,
* meaning that random(5, 5) will return 5 (useful)
* and random(7, 4) will return 7 (not useful.. better idea?)
*/
public final float random(float howsmall, float howbig) {
if (howsmall >= howbig) return howsmall;
float diff = howbig - howsmall;
return random(diff) + howsmall;
}
public final void randomSeed(long what) {
// internal random number object
if (internalRandom == null) internalRandom = new Random();
internalRandom.setSeed(what);
}
//////////////////////////////////////////////////////////////
// PERLIN NOISE
// [toxi 040903]
// octaves and amplitude amount per octave are now user controlled
// via the noiseDetail() function.
// [toxi 030902]
// cleaned up code and now using bagel's cosine table to speed up
// [toxi 030901]
// implementation by the german demo group farbrausch
// as used in their demo "art": http://www.farb-rausch.de/fr010src.zip
static final int PERLIN_YWRAPB = 4;
static final int PERLIN_YWRAP = 1<
* Generally, loadImage() should only be used during setup, because
* re-loading images inside draw() is likely to cause a significant
* delay while memory is allocated and the thread blocks while waiting
* for the image to load because loading is not asynchronous.
*
* To load several images asynchronously, see more information in the
* FAQ about writing your own threaded image loading method.
*
* As of 0096, returns null if no image of that name is found,
* rather than an error.
*
* Release 0115 also provides support for reading TIFF and RLE-encoded
* Targa (.tga) files written by Processing via save() and saveFrame().
* Other TIFF and Targa files will probably not load, use a different
* format (gif, jpg and png are safest bets) when creating images with
* another application to use with Processing.
*
* Also in release 0115, more image formats (BMP and others) can
* be read when using Java 1.4 and later. Because many people still
* use Java 1.1 and 1.3, these formats are not recommended for
* work that will be posted on the web. To get a list of possible
* image formats for use with Java 1.4 and later, use the following:
* println(javax.imageio.ImageIO.getReaderFormatNames())
*
* Images are loaded via a byte array that is passed to
* Toolkit.createImage(). Unfortunately, we cannot use Applet.getImage()
* because it takes a URL argument, which would be a pain in the a--
* to make work consistently for online and local sketches.
* Sometimes this causes problems, resulting in issues like
* Bug 279
* and
* Bug 305.
* In release 0115, everything was instead run through javax.imageio,
* but that turned out to be very slow, see
* Bug 392.
* As a result, starting with 0116, the following happens:
*
* Rewritten for 0115 to read/write RLE-encoded targa images.
* For 0125, non-RLE encoded images are now supported, along with
* images whose y-order is reversed (which is standard for TGA files).
*/
protected PImage loadImageTGA(String filename) throws IOException {
InputStream is = createInput(filename);
if (is == null) return null;
byte header[] = new byte[18];
int offset = 0;
do {
int count = is.read(header, offset, header.length - offset);
if (count == -1) return null;
offset += count;
} while (offset < 18);
/*
header[2] image type code
2 (0x02) - Uncompressed, RGB images.
3 (0x03) - Uncompressed, black and white images.
10 (0x0A) - Runlength encoded RGB images.
11 (0x0B) - Compressed, black and white images. (grayscale?)
header[16] is the bit depth (8, 24, 32)
header[17] image descriptor (packed bits)
0x20 is 32 = origin upper-left
0x28 is 32 + 8 = origin upper-left + 32 bits
7 6 5 4 3 2 1 0
128 64 32 16 8 4 2 1
*/
int format = 0;
if (((header[2] == 3) || (header[2] == 11)) && // B&W, plus RLE or not
(header[16] == 8) && // 8 bits
((header[17] == 0x8) || (header[17] == 0x28))) { // origin, 32 bit
format = ALPHA;
} else if (((header[2] == 2) || (header[2] == 10)) && // RGB, RLE or not
(header[16] == 24) && // 24 bits
((header[17] == 0x20) || (header[17] == 0))) { // origin
format = RGB;
} else if (((header[2] == 2) || (header[2] == 10)) &&
(header[16] == 32) &&
((header[17] == 0x8) || (header[17] == 0x28))) { // origin, 32
format = ARGB;
}
if (format == 0) {
System.err.println("Unknown .tga file format for " + filename);
//" (" + header[2] + " " +
//(header[16] & 0xff) + " " +
//hex(header[17], 2) + ")");
return null;
}
int w = ((header[13] & 0xff) << 8) + (header[12] & 0xff);
int h = ((header[15] & 0xff) << 8) + (header[14] & 0xff);
PImage outgoing = createImage(w, h, format);
// where "reversed" means upper-left corner (normal for most of
// the modernized world, but "reversed" for the tga spec)
boolean reversed = (header[17] & 0x20) != 0;
if ((header[2] == 2) || (header[2] == 3)) { // not RLE encoded
if (reversed) {
int index = (h-1) * w;
switch (format) {
case ALPHA:
for (int y = h-1; y >= 0; y--) {
for (int x = 0; x < w; x++) {
outgoing.pixels[index + x] = is.read();
}
index -= w;
}
break;
case RGB:
for (int y = h-1; y >= 0; y--) {
for (int x = 0; x < w; x++) {
outgoing.pixels[index + x] =
is.read() | (is.read() << 8) | (is.read() << 16) |
0xff000000;
}
index -= w;
}
break;
case ARGB:
for (int y = h-1; y >= 0; y--) {
for (int x = 0; x < w; x++) {
outgoing.pixels[index + x] =
is.read() | (is.read() << 8) | (is.read() << 16) |
(is.read() << 24);
}
index -= w;
}
}
} else { // not reversed
int count = w * h;
switch (format) {
case ALPHA:
for (int i = 0; i < count; i++) {
outgoing.pixels[i] = is.read();
}
break;
case RGB:
for (int i = 0; i < count; i++) {
outgoing.pixels[i] =
is.read() | (is.read() << 8) | (is.read() << 16) |
0xff000000;
}
break;
case ARGB:
for (int i = 0; i < count; i++) {
outgoing.pixels[i] =
is.read() | (is.read() << 8) | (is.read() << 16) |
(is.read() << 24);
}
break;
}
}
} else { // header[2] is 10 or 11
int index = 0;
int px[] = outgoing.pixels;
while (index < px.length) {
int num = is.read();
boolean isRLE = (num & 0x80) != 0;
if (isRLE) {
num -= 127; // (num & 0x7F) + 1
int pixel = 0;
switch (format) {
case ALPHA:
pixel = is.read();
break;
case RGB:
pixel = 0xFF000000 |
is.read() | (is.read() << 8) | (is.read() << 16);
//(is.read() << 16) | (is.read() << 8) | is.read();
break;
case ARGB:
pixel = is.read() |
(is.read() << 8) | (is.read() << 16) | (is.read() << 24);
break;
}
for (int i = 0; i < num; i++) {
px[index++] = pixel;
if (index == px.length) break;
}
} else { // write up to 127 bytes as uncompressed
num += 1;
switch (format) {
case ALPHA:
for (int i = 0; i < num; i++) {
px[index++] = is.read();
}
break;
case RGB:
for (int i = 0; i < num; i++) {
px[index++] = 0xFF000000 |
is.read() | (is.read() << 8) | (is.read() << 16);
//(is.read() << 16) | (is.read() << 8) | is.read();
}
break;
case ARGB:
for (int i = 0; i < num; i++) {
px[index++] = is.read() | //(is.read() << 24) |
(is.read() << 8) | (is.read() << 16) | (is.read() << 24);
//(is.read() << 16) | (is.read() << 8) | is.read();
}
break;
}
}
}
if (!reversed) {
int[] temp = new int[w];
for (int y = 0; y < h/2; y++) {
int z = (h-1) - y;
System.arraycopy(px, y*w, temp, 0, w);
System.arraycopy(px, z*w, px, y*w, w);
System.arraycopy(temp, 0, px, z*w, w);
}
}
}
return outgoing;
}
//////////////////////////////////////////////////////////////
// SHAPE I/O
/**
* Load a geometry from a file as a PShape. Currently only supports SVG data.
*/
public PShape loadShape(String filename) {
if (filename.toLowerCase().endsWith(".svg")) {
return new PShapeSVG(this, filename);
}
return null;
}
//////////////////////////////////////////////////////////////
// FONT I/O
public PFont loadFont(String filename) {
try {
InputStream input = createInput(filename);
return new PFont(input);
} catch (Exception e) {
die("Could not load font " + filename + ". " +
"Make sure that the font has been copied " +
"to the data folder of your sketch.", e);
}
return null;
}
public PFont createFont(String name, float size) {
return createFont(name, size, true, PFont.DEFAULT_CHARSET);
}
public PFont createFont(String name, float size, boolean smooth) {
return createFont(name, size, smooth, PFont.DEFAULT_CHARSET);
}
/**
* Create a .vlw font on the fly from either a font name that's
* installed on the system, or from a .ttf or .otf that's inside
* the data folder of this sketch.
*
* This method is useful if you want to use the facilities provided
* by PApplet to easily open things from the data folder or from a URL,
* but want an InputStream object so that you can use other Java
* methods to take more control of how the stream is read.
*
* If the requested item doesn't exist, null is returned.
* (Prior to 0096, die() would be called, killing the applet)
*
* For 0096+, the "data" folder is exported intact with subfolders,
* and openStream() properly handles subdirectories from the data folder
*
* If not online, this will also check to see if the user is asking
* for a file whose name isn't properly capitalized. This helps prevent
* issues when a sketch is exported to the web, where case sensitivity
* matters, as opposed to Windows and the Mac OS default where
* case sensitivity is preserved but ignored.
*
* It is strongly recommended that libraries use this method to open
* data files, so that the loading sequence is handled in the same way
* as functions like loadBytes(), loadImage(), etc.
*
* The filename passed in can be:
*
* Exceptions are handled internally, when an error, occurs, an
* exception is printed to the console and 'null' is returned,
* but the program continues running. This is a tradeoff between
* 1) showing the user that there was a problem but 2) not requiring
* that all i/o code is contained in try/catch blocks, for the sake
* of new users (or people who are just trying to get things done
* in a "scripting" fashion. If you want to handle exceptions,
* use Java methods for I/O.
*/
public String[] loadStrings(String filename) {
InputStream is = createInput(filename);
if (is != null) return loadStrings(is);
System.err.println("The file \"" + filename + "\" " +
"is missing or inaccessible, make sure " +
"the URL is valid or that the file has been " +
"added to your sketch and is readable.");
return null;
}
static public String[] loadStrings(InputStream input) {
try {
BufferedReader reader =
new BufferedReader(new InputStreamReader(input, "UTF-8"));
String lines[] = new String[100];
int lineCount = 0;
String line = null;
while ((line = reader.readLine()) != null) {
if (lineCount == lines.length) {
String temp[] = new String[lineCount << 1];
System.arraycopy(lines, 0, temp, 0, lineCount);
lines = temp;
}
lines[lineCount++] = line;
}
reader.close();
if (lineCount == lines.length) {
return lines;
}
// resize array to appropriate amount for these lines
String output[] = new String[lineCount];
System.arraycopy(lines, 0, output, 0, lineCount);
return output;
} catch (IOException e) {
e.printStackTrace();
//throw new RuntimeException("Error inside loadStrings()");
}
return null;
}
//////////////////////////////////////////////////////////////
// FILE OUTPUT
/**
* Similar to createInput() (formerly openStream), this creates a Java
* OutputStream for a given filename or path. The file will be created in
* the sketch folder, or in the same folder as an exported application.
*
* In this method, the data path is defined not as the applet's actual
* data path, but a folder titled "data" in the sketch's working
* directory. When running inside the PDE, this will be the sketch's
* "data" folder. However, when exported (as application or applet),
* sketch's data folder is exported as part of the applications jar file,
* and it's not possible to read/write from the jar file in a generic way.
* If you need to read data from the jar file, you should use other methods
* such as createInput(), createReader(), or loadStrings().
*/
public String dataPath(String where) {
// isAbsolute() could throw an access exception, but so will writing
// to the local disk using the sketch path, so this is safe here.
if (new File(where).isAbsolute()) return where;
return sketchPath + File.separator + "data" + File.separator + where;
}
/**
* Return a full path to an item in the data folder as a File object.
* See the dataPath() method for more information.
*/
public File dataFile(String where) {
return new File(dataPath(where));
}
/**
* Takes a path and creates any in-between folders if they don't
* already exist. Useful when trying to save to a subfolder that
* may not actually exist.
*/
static public void createPath(String path) {
createPath(new File(path));
}
static public void createPath(File file) {
try {
String parent = file.getParent();
if (parent != null) {
File unit = new File(parent);
if (!unit.exists()) unit.mkdirs();
}
} catch (SecurityException se) {
System.err.println("You don't have permissions to create " +
file.getAbsolutePath());
}
}
//////////////////////////////////////////////////////////////
// SORT
static public byte[] sort(byte what[]) {
return sort(what, what.length);
}
static public byte[] sort(byte[] what, int count) {
byte[] outgoing = new byte[what.length];
System.arraycopy(what, 0, outgoing, 0, what.length);
Arrays.sort(outgoing, 0, count);
return outgoing;
}
static public char[] sort(char what[]) {
return sort(what, what.length);
}
static public char[] sort(char[] what, int count) {
char[] outgoing = new char[what.length];
System.arraycopy(what, 0, outgoing, 0, what.length);
Arrays.sort(outgoing, 0, count);
return outgoing;
}
static public int[] sort(int what[]) {
return sort(what, what.length);
}
static public int[] sort(int[] what, int count) {
int[] outgoing = new int[what.length];
System.arraycopy(what, 0, outgoing, 0, what.length);
Arrays.sort(outgoing, 0, count);
return outgoing;
}
static public float[] sort(float what[]) {
return sort(what, what.length);
}
static public float[] sort(float[] what, int count) {
float[] outgoing = new float[what.length];
System.arraycopy(what, 0, outgoing, 0, what.length);
Arrays.sort(outgoing, 0, count);
return outgoing;
}
static public String[] sort(String what[]) {
return sort(what, what.length);
}
static public String[] sort(String[] what, int count) {
String[] outgoing = new String[what.length];
System.arraycopy(what, 0, outgoing, 0, what.length);
Arrays.sort(outgoing, 0, count);
return outgoing;
}
//////////////////////////////////////////////////////////////
// ARRAY UTILITIES
/**
* Calls System.arraycopy(), included here so that we can
* avoid people needing to learn about the System object
* before they can just copy an array.
*/
static public void arrayCopy(Object src, int srcPosition,
Object dst, int dstPosition,
int length) {
System.arraycopy(src, srcPosition, dst, dstPosition, length);
}
/**
* Convenience method for arraycopy().
* Identical to
* To use this on numbers, first pass the array to nf() or nfs()
* to get a list of String objects, then use join on that.
*
* The whitespace characters are "\t\n\r\f", which are the defaults
* for java.util.StringTokenizer, plus the unicode non-breaking space
* character, which is found commonly on files created by or used
* in conjunction with Mac OS X (character 160, or 0x00A0 in hex).
*
* This operates differently than the others, where the
* single delimeter is the only breaking point, and consecutive
* delimeters will produce an empty string (""). This way,
* one can split on tab characters, but maintain the column
* alignments (of say an excel file) where there are empty columns.
*/
static public String[] split(String what, char delim) {
// do this so that the exception occurs inside the user's
// program, rather than appearing to be a bug inside split()
if (what == null) return null;
//return split(what, String.valueOf(delim)); // huh
char chars[] = what.toCharArray();
int splitCount = 0; //1;
for (int i = 0; i < chars.length; i++) {
if (chars[i] == delim) splitCount++;
}
// make sure that there is something in the input string
//if (chars.length > 0) {
// if the last char is a delimeter, get rid of it..
//if (chars[chars.length-1] == delim) splitCount--;
// on second thought, i don't agree with this, will disable
//}
if (splitCount == 0) {
String splits[] = new String[1];
splits[0] = new String(what);
return splits;
}
//int pieceCount = splitCount + 1;
String splits[] = new String[splitCount + 1];
int splitIndex = 0;
int startIndex = 0;
for (int i = 0; i < chars.length; i++) {
if (chars[i] == delim) {
splits[splitIndex++] =
new String(chars, startIndex, i-startIndex);
startIndex = i + 1;
}
}
//if (startIndex != chars.length) {
splits[splitIndex] =
new String(chars, startIndex, chars.length-startIndex);
//}
return splits;
}
/**
* Split a String on a specific delimiter. Unlike Java's String.split()
* method, this does not parse the delimiter as a regexp because it's more
* confusing than necessary, and String.split() is always available for
* those who want regexp.
*/
static public String[] split(String what, String delim) {
ArrayList Convert an integer to a boolean. Because of how Java handles upgrading
* numbers, this will also cover byte and char (as they will upgrade to
* an int without any sort of explicit cast). The preprocessor will convert boolean(what) to parseBoolean(what).
* The options shown here are not yet finalized and will be
* changing over the next several releases.
*
* The simplest way to turn and applet into an application is to
* add the following code to your program:
*
*
* For releases 0116 and later, if you have problems such as those seen
* in Bugs 279 and 305, use Applet.getImage() instead. You'll be stuck
* with the limitations of getImage() (the headache of dealing with
* online/offline use). Set up your own MediaTracker, and pass the resulting
* java.awt.Image to the PImage constructor that takes an AWT image.
*/
public PImage loadImage(String filename) {
return loadImage(filename, null);
}
/**
* Identical to loadImage, but allows you to specify the type of
* image by its extension. Especially useful when downloading from
* CGI scripts.
*
* Use 'unknown' as the extension to pass off to the default
* image loader that handles gif, jpg, and png.
*/
public PImage loadImage(String filename, String extension) {
if (extension == null) {
String lower = filename.toLowerCase();
int dot = filename.lastIndexOf('.');
if (dot == -1) {
extension = "unknown"; // no extension found
}
extension = lower.substring(dot + 1);
// check for, and strip any parameters on the url, i.e.
// filename.jpg?blah=blah&something=that
int question = extension.indexOf('?');
if (question != -1) {
extension = extension.substring(0, question);
}
}
// just in case. them users will try anything!
extension = extension.toLowerCase();
if (extension.equals("tga")) {
try {
return loadImageTGA(filename);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
if (extension.equals("tif") || extension.equals("tiff")) {
byte bytes[] = loadBytes(filename);
return (bytes == null) ? null : PImage.loadTIFF(bytes);
}
// For jpeg, gif, and png, load them using createImage(),
// because the javax.imageio code was found to be much slower, see
// Bug 392.
try {
if (extension.equals("jpg") || extension.equals("jpeg") ||
extension.equals("gif") || extension.equals("png") ||
extension.equals("unknown")) {
byte bytes[] = loadBytes(filename);
if (bytes == null) {
return null;
} else {
Image awtImage = Toolkit.getDefaultToolkit().createImage(bytes);
PImage image = loadImageMT(awtImage);
if (image.width == -1) {
System.err.println("The file " + filename +
" contains bad image data, or may not be an image.");
}
// if it's a .gif image, test to see if it has transparency
if (extension.equals("gif") || extension.equals("png")) {
image.checkAlpha();
}
return image;
}
}
} catch (Exception e) {
// show error, but move on to the stuff below, see if it'll work
e.printStackTrace();
}
if (loadImageFormats == null) {
loadImageFormats = ImageIO.getReaderFormatNames();
}
if (loadImageFormats != null) {
for (int i = 0; i < loadImageFormats.length; i++) {
if (extension.equals(loadImageFormats[i])) {
return loadImageIO(filename);
}
}
}
// failed, could not load image after all those attempts
System.err.println("Could not find a method to load " + filename);
return null;
}
public PImage requestImage(String filename) {
return requestImage(filename, null);
}
public PImage requestImage(String filename, String extension) {
PImage vessel = createImage(0, 0, ARGB);
AsyncImageLoader ail =
new AsyncImageLoader(filename, extension, vessel);
ail.start();
return vessel;
}
/**
* By trial and error, four image loading threads seem to work best when
* loading images from online. This is consistent with the number of open
* connections that web browsers will maintain. The variable is made public
* (however no accessor has been added since it's esoteric) if you really
* want to have control over the value used. For instance, when loading local
* files, it might be better to only have a single thread (or two) loading
* images so that you're disk isn't simply jumping around.
*/
public int requestImageMax = 4;
volatile int requestImageCount;
class AsyncImageLoader extends Thread {
String filename;
String extension;
PImage vessel;
public AsyncImageLoader(String filename, String extension, PImage vessel) {
this.filename = filename;
this.extension = extension;
this.vessel = vessel;
}
public void run() {
while (requestImageCount == requestImageMax) {
try {
Thread.sleep(10);
} catch (InterruptedException e) { }
}
requestImageCount++;
PImage actual = loadImage(filename, extension);
// An error message should have already printed
if (actual == null) {
vessel.width = -1;
vessel.height = -1;
} else {
vessel.width = actual.width;
vessel.height = actual.height;
vessel.format = actual.format;
vessel.pixels = actual.pixels;
}
requestImageCount--;
}
}
/**
* Load an AWT image synchronously by setting up a MediaTracker for
* a single image, and blocking until it has loaded.
*/
protected PImage loadImageMT(Image awtImage) {
MediaTracker tracker = new MediaTracker(this);
tracker.addImage(awtImage, 0);
try {
tracker.waitForAll();
} catch (InterruptedException e) {
//e.printStackTrace(); // non-fatal, right?
}
PImage image = new PImage(awtImage);
image.parent = this;
return image;
}
/**
* Use Java 1.4 ImageIO methods to load an image.
*/
protected PImage loadImageIO(String filename) {
InputStream stream = createInput(filename);
if (stream == null) {
System.err.println("The image " + filename + " could not be found.");
return null;
}
try {
BufferedImage bi = ImageIO.read(stream);
PImage outgoing = new PImage(bi.getWidth(), bi.getHeight());
outgoing.parent = this;
bi.getRGB(0, 0, outgoing.width, outgoing.height,
outgoing.pixels, 0, outgoing.width);
// check the alpha for this image
// was gonna call getType() on the image to see if RGB or ARGB,
// but it's not actually useful, since gif images will come through
// as TYPE_BYTE_INDEXED, which means it'll still have to check for
// the transparency. also, would have to iterate through all the other
// types and guess whether alpha was in there, so.. just gonna stick
// with the old method.
outgoing.checkAlpha();
// return the image
return outgoing;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Targa image loader for RLE-compressed TGA files.
*
*
*/
public InputStream createInput(String filename) {
InputStream input = createInputRaw(filename);
if ((input != null) && filename.toLowerCase().endsWith(".gz")) {
try {
return new GZIPInputStream(input);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
return input;
}
/**
* Call openStream() without automatic gzip decompression.
*/
public InputStream createInputRaw(String filename) {
InputStream stream = null;
if (filename == null) return null;
if (filename.length() == 0) {
// an error will be called by the parent function
//System.err.println("The filename passed to openStream() was empty.");
return null;
}
// safe to check for this as a url first. this will prevent online
// access logs from being spammed with GET /sketchfolder/http://blahblah
try {
URL url = new URL(filename);
stream = url.openStream();
return stream;
} catch (MalformedURLException mfue) {
// not a url, that's fine
} catch (FileNotFoundException fnfe) {
// Java 1.5 likes to throw this when URL not available. (fix for 0119)
// http://dev.processing.org/bugs/show_bug.cgi?id=403
} catch (IOException e) {
// changed for 0117, shouldn't be throwing exception
e.printStackTrace();
//System.err.println("Error downloading from URL " + filename);
return null;
//throw new RuntimeException("Error downloading from URL " + filename);
}
// Moved this earlier than the getResourceAsStream() checks, because
// calling getResourceAsStream() on a directory lists its contents.
// http://dev.processing.org/bugs/show_bug.cgi?id=716
try {
// First see if it's in a data folder. This may fail by throwing
// a SecurityException. If so, this whole block will be skipped.
File file = new File(dataPath(filename));
if (!file.exists()) {
// next see if it's just in the sketch folder
file = new File(sketchPath, filename);
}
if (file.isDirectory()) {
return null;
}
if (file.exists()) {
try {
// handle case sensitivity check
String filePath = file.getCanonicalPath();
String filenameActual = new File(filePath).getName();
// make sure there isn't a subfolder prepended to the name
String filenameShort = new File(filename).getName();
// if the actual filename is the same, but capitalized
// differently, warn the user.
//if (filenameActual.equalsIgnoreCase(filenameShort) &&
//!filenameActual.equals(filenameShort)) {
if (!filenameActual.equals(filenameShort)) {
throw new RuntimeException("This file is named " +
filenameActual + " not " +
filename + ". Rename the file " +
"or change your code.");
}
} catch (IOException e) { }
}
// if this file is ok, may as well just load it
stream = new FileInputStream(file);
if (stream != null) return stream;
// have to break these out because a general Exception might
// catch the RuntimeException being thrown above
} catch (IOException ioe) {
} catch (SecurityException se) { }
// Using getClassLoader() prevents java from converting dots
// to slashes or requiring a slash at the beginning.
// (a slash as a prefix means that it'll load from the root of
// the jar, rather than trying to dig into the package location)
ClassLoader cl = getClass().getClassLoader();
// by default, data files are exported to the root path of the jar.
// (not the data folder) so check there first.
stream = cl.getResourceAsStream("data/" + filename);
if (stream != null) {
String cn = stream.getClass().getName();
// this is an irritation of sun's java plug-in, which will return
// a non-null stream for an object that doesn't exist. like all good
// things, this is probably introduced in java 1.5. awesome!
// http://dev.processing.org/bugs/show_bug.cgi?id=359
if (!cn.equals("sun.plugin.cache.EmptyInputStream")) {
return stream;
}
}
// When used with an online script, also need to check without the
// data folder, in case it's not in a subfolder called 'data'.
// http://dev.processing.org/bugs/show_bug.cgi?id=389
stream = cl.getResourceAsStream(filename);
if (stream != null) {
String cn = stream.getClass().getName();
if (!cn.equals("sun.plugin.cache.EmptyInputStream")) {
return stream;
}
}
try {
// attempt to load from a local file, used when running as
// an application, or as a signed applet
try { // first try to catch any security exceptions
try {
stream = new FileInputStream(dataPath(filename));
if (stream != null) return stream;
} catch (IOException e2) { }
try {
stream = new FileInputStream(sketchPath(filename));
if (stream != null) return stream;
} catch (Exception e) { } // ignored
try {
stream = new FileInputStream(filename);
if (stream != null) return stream;
} catch (IOException e1) { }
} catch (SecurityException se) { } // online, whups
} catch (Exception e) {
//die(e.getMessage(), e);
e.printStackTrace();
}
return null;
}
static public InputStream createInput(File file) {
try {
InputStream input = new FileInputStream(file);
if (file.getName().toLowerCase().endsWith(".gz")) {
return new GZIPInputStream(input);
}
return input;
} catch (IOException e) {
if (file == null) {
throw new RuntimeException("File passed to openStream() was null");
} else {
e.printStackTrace();
throw new RuntimeException("Couldn't openStream() for " +
file.getAbsolutePath());
}
}
}
public byte[] loadBytes(String filename) {
InputStream is = createInput(filename);
if (is != null) return loadBytes(is);
System.err.println("The file \"" + filename + "\" " +
"is missing or inaccessible, make sure " +
"the URL is valid or that the file has been " +
"added to your sketch and is readable.");
return null;
}
static public byte[] loadBytes(InputStream input) {
try {
BufferedInputStream bis = new BufferedInputStream(input);
ByteArrayOutputStream out = new ByteArrayOutputStream();
int c = bis.read();
while (c != -1) {
out.write(c);
c = bis.read();
}
return out.toByteArray();
} catch (IOException e) {
e.printStackTrace();
//throw new RuntimeException("Couldn't load bytes from stream");
}
return null;
}
static public byte[] loadBytes(File file) {
InputStream is = createInput(file);
return loadBytes(is);
}
static public String[] loadStrings(File file) {
InputStream is = createInput(file);
if (is != null) return loadStrings(is);
return null;
}
/**
* Load data from a file and shove it into a String array.
* arraycopy(src, 0, dst, 0, length);
*/
static public void arrayCopy(Object src, Object dst, int length) {
System.arraycopy(src, 0, dst, 0, length);
}
/**
* Shortcut to copy the entire contents of
* the source into the destination array.
* Identical to arraycopy(src, 0, dst, 0, src.length);
*/
static public void arrayCopy(Object src, Object dst) {
System.arraycopy(src, 0, dst, 0, Array.getLength(src));
}
//
/**
* @deprecated Use arrayCopy() instead.
*/
static public void arraycopy(Object src, int srcPosition,
Object dst, int dstPosition,
int length) {
System.arraycopy(src, srcPosition, dst, dstPosition, length);
}
/**
* @deprecated Use arrayCopy() instead.
*/
static public void arraycopy(Object src, Object dst, int length) {
System.arraycopy(src, 0, dst, 0, length);
}
/**
* @deprecated Use arrayCopy() instead.
*/
static public void arraycopy(Object src, Object dst) {
System.arraycopy(src, 0, dst, 0, Array.getLength(src));
}
//
static public boolean[] expand(boolean list[]) {
return expand(list, list.length << 1);
}
static public boolean[] expand(boolean list[], int newSize) {
boolean temp[] = new boolean[newSize];
System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length));
return temp;
}
static public byte[] expand(byte list[]) {
return expand(list, list.length << 1);
}
static public byte[] expand(byte list[], int newSize) {
byte temp[] = new byte[newSize];
System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length));
return temp;
}
static public char[] expand(char list[]) {
return expand(list, list.length << 1);
}
static public char[] expand(char list[], int newSize) {
char temp[] = new char[newSize];
System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length));
return temp;
}
static public int[] expand(int list[]) {
return expand(list, list.length << 1);
}
static public int[] expand(int list[], int newSize) {
int temp[] = new int[newSize];
System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length));
return temp;
}
static public float[] expand(float list[]) {
return expand(list, list.length << 1);
}
static public float[] expand(float list[], int newSize) {
float temp[] = new float[newSize];
System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length));
return temp;
}
static public String[] expand(String list[]) {
return expand(list, list.length << 1);
}
static public String[] expand(String list[], int newSize) {
String temp[] = new String[newSize];
// in case the new size is smaller than list.length
System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length));
return temp;
}
static public Object expand(Object array) {
return expand(array, Array.getLength(array) << 1);
}
static public Object expand(Object list, int newSize) {
Class> type = list.getClass().getComponentType();
Object temp = Array.newInstance(type, newSize);
System.arraycopy(list, 0, temp, 0,
Math.min(Array.getLength(list), newSize));
return temp;
}
//
// contract() has been removed in revision 0124, use subset() instead.
// (expand() is also functionally equivalent)
//
static public byte[] append(byte b[], byte value) {
b = expand(b, b.length + 1);
b[b.length-1] = value;
return b;
}
static public char[] append(char b[], char value) {
b = expand(b, b.length + 1);
b[b.length-1] = value;
return b;
}
static public int[] append(int b[], int value) {
b = expand(b, b.length + 1);
b[b.length-1] = value;
return b;
}
static public float[] append(float b[], float value) {
b = expand(b, b.length + 1);
b[b.length-1] = value;
return b;
}
static public String[] append(String b[], String value) {
b = expand(b, b.length + 1);
b[b.length-1] = value;
return b;
}
static public Object append(Object b, Object value) {
int length = Array.getLength(b);
b = expand(b, length + 1);
Array.set(b, length, value);
return b;
}
//
static public boolean[] shorten(boolean list[]) {
return subset(list, 0, list.length-1);
}
static public byte[] shorten(byte list[]) {
return subset(list, 0, list.length-1);
}
static public char[] shorten(char list[]) {
return subset(list, 0, list.length-1);
}
static public int[] shorten(int list[]) {
return subset(list, 0, list.length-1);
}
static public float[] shorten(float list[]) {
return subset(list, 0, list.length-1);
}
static public String[] shorten(String list[]) {
return subset(list, 0, list.length-1);
}
static public Object shorten(Object list) {
int length = Array.getLength(list);
return subset(list, 0, length - 1);
}
//
static final public boolean[] splice(boolean list[],
boolean v, int index) {
boolean outgoing[] = new boolean[list.length + 1];
System.arraycopy(list, 0, outgoing, 0, index);
outgoing[index] = v;
System.arraycopy(list, index, outgoing, index + 1,
list.length - index);
return outgoing;
}
static final public boolean[] splice(boolean list[],
boolean v[], int index) {
boolean outgoing[] = new boolean[list.length + v.length];
System.arraycopy(list, 0, outgoing, 0, index);
System.arraycopy(v, 0, outgoing, index, v.length);
System.arraycopy(list, index, outgoing, index + v.length,
list.length - index);
return outgoing;
}
static final public byte[] splice(byte list[],
byte v, int index) {
byte outgoing[] = new byte[list.length + 1];
System.arraycopy(list, 0, outgoing, 0, index);
outgoing[index] = v;
System.arraycopy(list, index, outgoing, index + 1,
list.length - index);
return outgoing;
}
static final public byte[] splice(byte list[],
byte v[], int index) {
byte outgoing[] = new byte[list.length + v.length];
System.arraycopy(list, 0, outgoing, 0, index);
System.arraycopy(v, 0, outgoing, index, v.length);
System.arraycopy(list, index, outgoing, index + v.length,
list.length - index);
return outgoing;
}
static final public char[] splice(char list[],
char v, int index) {
char outgoing[] = new char[list.length + 1];
System.arraycopy(list, 0, outgoing, 0, index);
outgoing[index] = v;
System.arraycopy(list, index, outgoing, index + 1,
list.length - index);
return outgoing;
}
static final public char[] splice(char list[],
char v[], int index) {
char outgoing[] = new char[list.length + v.length];
System.arraycopy(list, 0, outgoing, 0, index);
System.arraycopy(v, 0, outgoing, index, v.length);
System.arraycopy(list, index, outgoing, index + v.length,
list.length - index);
return outgoing;
}
static final public int[] splice(int list[],
int v, int index) {
int outgoing[] = new int[list.length + 1];
System.arraycopy(list, 0, outgoing, 0, index);
outgoing[index] = v;
System.arraycopy(list, index, outgoing, index + 1,
list.length - index);
return outgoing;
}
static final public int[] splice(int list[],
int v[], int index) {
int outgoing[] = new int[list.length + v.length];
System.arraycopy(list, 0, outgoing, 0, index);
System.arraycopy(v, 0, outgoing, index, v.length);
System.arraycopy(list, index, outgoing, index + v.length,
list.length - index);
return outgoing;
}
static final public float[] splice(float list[],
float v, int index) {
float outgoing[] = new float[list.length + 1];
System.arraycopy(list, 0, outgoing, 0, index);
outgoing[index] = v;
System.arraycopy(list, index, outgoing, index + 1,
list.length - index);
return outgoing;
}
static final public float[] splice(float list[],
float v[], int index) {
float outgoing[] = new float[list.length + v.length];
System.arraycopy(list, 0, outgoing, 0, index);
System.arraycopy(v, 0, outgoing, index, v.length);
System.arraycopy(list, index, outgoing, index + v.length,
list.length - index);
return outgoing;
}
static final public String[] splice(String list[],
String v, int index) {
String outgoing[] = new String[list.length + 1];
System.arraycopy(list, 0, outgoing, 0, index);
outgoing[index] = v;
System.arraycopy(list, index, outgoing, index + 1,
list.length - index);
return outgoing;
}
static final public String[] splice(String list[],
String v[], int index) {
String outgoing[] = new String[list.length + v.length];
System.arraycopy(list, 0, outgoing, 0, index);
System.arraycopy(v, 0, outgoing, index, v.length);
System.arraycopy(list, index, outgoing, index + v.length,
list.length - index);
return outgoing;
}
static final public Object splice(Object list, Object v, int index) {
Object[] outgoing = null;
int length = Array.getLength(list);
// check whether item being spliced in is an array
if (v.getClass().getName().charAt(0) == '[') {
int vlength = Array.getLength(v);
outgoing = new Object[length + vlength];
System.arraycopy(list, 0, outgoing, 0, index);
System.arraycopy(v, 0, outgoing, index, vlength);
System.arraycopy(list, index, outgoing, index + vlength, length - index);
} else {
outgoing = new Object[length + 1];
System.arraycopy(list, 0, outgoing, 0, index);
Array.set(outgoing, index, v);
System.arraycopy(list, index, outgoing, index + 1, length - index);
}
return outgoing;
}
//
static public boolean[] subset(boolean list[], int start) {
return subset(list, start, list.length - start);
}
static public boolean[] subset(boolean list[], int start, int count) {
boolean output[] = new boolean[count];
System.arraycopy(list, start, output, 0, count);
return output;
}
static public byte[] subset(byte list[], int start) {
return subset(list, start, list.length - start);
}
static public byte[] subset(byte list[], int start, int count) {
byte output[] = new byte[count];
System.arraycopy(list, start, output, 0, count);
return output;
}
static public char[] subset(char list[], int start) {
return subset(list, start, list.length - start);
}
static public char[] subset(char list[], int start, int count) {
char output[] = new char[count];
System.arraycopy(list, start, output, 0, count);
return output;
}
static public int[] subset(int list[], int start) {
return subset(list, start, list.length - start);
}
static public int[] subset(int list[], int start, int count) {
int output[] = new int[count];
System.arraycopy(list, start, output, 0, count);
return output;
}
static public float[] subset(float list[], int start) {
return subset(list, start, list.length - start);
}
static public float[] subset(float list[], int start, int count) {
float output[] = new float[count];
System.arraycopy(list, start, output, 0, count);
return output;
}
static public String[] subset(String list[], int start) {
return subset(list, start, list.length - start);
}
static public String[] subset(String list[], int start, int count) {
String output[] = new String[count];
System.arraycopy(list, start, output, 0, count);
return output;
}
static public Object subset(Object list, int start) {
int length = Array.getLength(list);
return subset(list, start, length - start);
}
static public Object subset(Object list, int start, int count) {
Class> type = list.getClass().getComponentType();
Object outgoing = Array.newInstance(type, count);
System.arraycopy(list, start, outgoing, 0, count);
return outgoing;
}
//
static public boolean[] concat(boolean a[], boolean b[]) {
boolean c[] = new boolean[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}
static public byte[] concat(byte a[], byte b[]) {
byte c[] = new byte[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}
static public char[] concat(char a[], char b[]) {
char c[] = new char[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}
static public int[] concat(int a[], int b[]) {
int c[] = new int[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}
static public float[] concat(float a[], float b[]) {
float c[] = new float[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}
static public String[] concat(String a[], String b[]) {
String c[] = new String[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}
static public Object concat(Object a, Object b) {
Class> type = a.getClass().getComponentType();
int alength = Array.getLength(a);
int blength = Array.getLength(b);
Object outgoing = Array.newInstance(type, alength + blength);
System.arraycopy(a, 0, outgoing, 0, alength);
System.arraycopy(b, 0, outgoing, alength, blength);
return outgoing;
}
//
static public boolean[] reverse(boolean list[]) {
boolean outgoing[] = new boolean[list.length];
int length1 = list.length - 1;
for (int i = 0; i < list.length; i++) {
outgoing[i] = list[length1 - i];
}
return outgoing;
}
static public byte[] reverse(byte list[]) {
byte outgoing[] = new byte[list.length];
int length1 = list.length - 1;
for (int i = 0; i < list.length; i++) {
outgoing[i] = list[length1 - i];
}
return outgoing;
}
static public char[] reverse(char list[]) {
char outgoing[] = new char[list.length];
int length1 = list.length - 1;
for (int i = 0; i < list.length; i++) {
outgoing[i] = list[length1 - i];
}
return outgoing;
}
static public int[] reverse(int list[]) {
int outgoing[] = new int[list.length];
int length1 = list.length - 1;
for (int i = 0; i < list.length; i++) {
outgoing[i] = list[length1 - i];
}
return outgoing;
}
static public float[] reverse(float list[]) {
float outgoing[] = new float[list.length];
int length1 = list.length - 1;
for (int i = 0; i < list.length; i++) {
outgoing[i] = list[length1 - i];
}
return outgoing;
}
static public String[] reverse(String list[]) {
String outgoing[] = new String[list.length];
int length1 = list.length - 1;
for (int i = 0; i < list.length; i++) {
outgoing[i] = list[length1 - i];
}
return outgoing;
}
static public Object reverse(Object list) {
Class> type = list.getClass().getComponentType();
int length = Array.getLength(list);
Object outgoing = Array.newInstance(type, length);
for (int i = 0; i < length; i++) {
Array.set(outgoing, i, Array.get(list, (length - 1) - i));
}
return outgoing;
}
//////////////////////////////////////////////////////////////
// STRINGS
/**
* Remove whitespace characters from the beginning and ending
* of a String. Works like String.trim() but includes the
* unicode nbsp character as well.
*/
static public String trim(String str) {
return str.replace('\u00A0', ' ').trim();
}
/**
* Trim the whitespace from a String array. This returns a new
* array and does not affect the passed-in array.
*/
static public String[] trim(String[] array) {
String[] outgoing = new String[array.length];
for (int i = 0; i < array.length; i++) {
outgoing[i] = array[i].replace('\u00A0', ' ').trim();
}
return outgoing;
}
/**
* Join an array of Strings together as a single String,
* separated by the whatever's passed in for the separator.
*/
static public String join(String str[], char separator) {
return join(str, String.valueOf(separator));
}
/**
* Join an array of Strings together as a single String,
* separated by the whatever's passed in for the separator.
*
* e.g. String stuff[] = { "apple", "bear", "cat" };
* String list = join(stuff, ", ");
* // list is now "apple, bear, cat"
*/
static public String join(String str[], String separator) {
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < str.length; i++) {
if (i != 0) buffer.append(separator);
buffer.append(str[i]);
}
return buffer.toString();
}
/**
* Split the provided String at wherever whitespace occurs.
* Multiple whitespace (extra spaces or tabs or whatever)
* between items will count as a single break.
*
* i.e. splitTokens("a b") -> { "a", "b" }
* splitTokens("a b") -> { "a", "b" }
* splitTokens("a\tb") -> { "a", "b" }
* splitTokens("a \t b ") -> { "a", "b" }
*/
static public String[] splitTokens(String what) {
return splitTokens(what, WHITESPACE);
}
/**
* Splits a string into pieces, using any of the chars in the
* String 'delim' as separator characters. For instance,
* in addition to white space, you might want to treat commas
* as a separator. The delimeter characters won't appear in
* the returned String array.
*
* i.e. splitTokens("a, b", " ,") -> { "a", "b" }
*
* To include all the whitespace possibilities, use the variable
* WHITESPACE, found in PConstants:
*
* i.e. splitTokens("a | b", WHITESPACE + "|"); -> { "a", "b" }
*/
static public String[] splitTokens(String what, String delim) {
StringTokenizer toker = new StringTokenizer(what, delim);
String pieces[] = new String[toker.countTokens()];
int index = 0;
while (toker.hasMoreTokens()) {
pieces[index++] = toker.nextToken();
}
return pieces;
}
/**
* Split a string into pieces along a specific character.
* Most commonly used to break up a String along a space or a tab
* character.
* static public void main(String args[]) {
* PApplet.main(new String[] { "YourSketchName" });
* }
* This will properly launch your applet from a double-clickable
* .jar or from the command line.
*
* Parameters useful for launching or also used by the PDE:
*
* --location=x,y upper-lefthand corner of where the applet
* should appear on screen. if not used,
* the default is to center on the main screen.
*
* --present put the applet into full screen presentation
* mode. requires java 1.4 or later.
*
* --exclusive use full screen exclusive mode when presenting.
* disables new windows or interaction with other
* monitors, this is like a "game" mode.
*
* --hide-stop use to hide the stop button in situations where
* you don't want to allow users to exit. also
* see the FAQ on information for capturing the ESC
* key when running in presentation mode.
*
* --stop-color=#xxxxxx color of the 'stop' text used to quit an
* sketch when it's in present mode.
*
* --bgcolor=#xxxxxx background color of the window.
*
* --sketch-path location of where to save files from functions
* like saveStrings() or saveFrame(). defaults to
* the folder that the java application was
* launched from, which means if this isn't set by
* the pde, everything goes into the same folder
* as processing.exe.
*
* --display=n set what display should be used by this applet.
* displays are numbered starting from 1.
*
* Parameters used by Processing when running via the PDE
*
* --external set when the applet is being used by the PDE
*
* --editor-location=x,y position of the upper-lefthand corner of the
* editor window, for placement of applet window
*
*/
static public void main(String args[]) {
// Disable abyssmally slow Sun renderer on OS X 10.5.
if (platform == MACOSX) {
// Only run this on OS X otherwise it can cause a permissions error.
// http://dev.processing.org/bugs/show_bug.cgi?id=976
System.setProperty("apple.awt.graphics.UseQuartz", "true");
}
// This doesn't do anything.
// if (platform == WINDOWS) {
// // For now, disable the D3D renderer on Java 6u10 because
// // it causes problems with Present mode.
// // http://dev.processing.org/bugs/show_bug.cgi?id=1009
// System.setProperty("sun.java2d.d3d", "false");
// }
if (args.length < 1) {
System.err.println("Usage: PApplet