diff --git a/core/preproc/.classpath b/core/preproc/.classpath
new file mode 100644
index 000000000..d9132e9f4
--- /dev/null
+++ b/core/preproc/.classpath
@@ -0,0 +1,7 @@
+
+
+ * 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:
+ *
+ * This is called when a sketch is shut down and this renderer was
+ * specified using the size() command, or inside endRecord() and
+ * endRaw(), in order to shut things off.
+ */
+ public void dispose() { // ignore
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // FRAME
+
+
+ /**
+ * Some renderers have requirements re: when they are ready to draw.
+ */
+ public boolean canDraw() { // ignore
+ return true;
+ }
+
+
+ /**
+ * Prepares the PGraphics for drawing.
+ *
+ * For the most part, hints are temporary api quirks,
+ * for which a proper api hasn't been properly worked out.
+ * for instance SMOOTH_IMAGES existed because smooth()
+ * wasn't yet implemented, but it will soon go away.
+ *
+ * They also exist for obscure features in the graphics
+ * engine, like enabling/disabling single pixel lines
+ * that ignore the zbuffer, the way they do in alphabot.
+ *
+ * Current hint options:
+ *
+ * Differences between beginShape() and line() and point() methods.
+ *
+ * beginShape() is intended to be more flexible at the expense of being
+ * a little more complicated to use. it handles more complicated shapes
+ * that can consist of many connected lines (so you get joins) or lines
+ * mixed with curves.
+ *
+ * The line() and point() command are for the far more common cases
+ * (particularly for our audience) that simply need to draw a line
+ * or a point on the screen.
+ *
+ * From the code side of things, line() may or may not call beginShape()
+ * to do the drawing. In the beta code, they do, but in the alpha code,
+ * they did not. they might be implemented one way or the other depending
+ * on tradeoffs of runtime efficiency vs. implementation efficiency &mdash
+ * meaning the speed that things run at vs. the speed it takes me to write
+ * the code and maintain it. for beta, the latter is most important so
+ * that's how things are implemented.
+ */
+ public void beginShape(int kind) {
+ shape = kind;
+ }
+
+
+ /**
+ * Sets whether the upcoming vertex is part of an edge.
+ * Equivalent to glEdgeFlag(), for people familiar with OpenGL.
+ */
+ public void edge(boolean edge) {
+ this.edge = edge;
+ }
+
+
+ /**
+ * Sets the current normal vector. Only applies with 3D rendering
+ * and inside a beginShape/endShape block.
+ *
+ * Implementation notes:
+ *
+ * cache all the points of the sphere in a static array
+ * top and bottom are just a bunch of triangles that land
+ * in the center point
+ *
+ * sphere is a series of concentric circles who radii vary
+ * along the shape, based on, er.. cos or something
+ *
+ * For instance, to convert the following example:
+ * Identical to typing:
+ *
+ * (This function is not optimized, since it's not expected to
+ * be called all that often. there are many juicy and obvious
+ * opimizations in here, but it's probably better to keep the
+ * code more readable)
+ */
+ protected void curveInit() {
+ // allocate only if/when used to save startup time
+ if (curveDrawMatrix == null) {
+ curveBasisMatrix = new PMatrix3D();
+ curveDrawMatrix = new PMatrix3D();
+ curveInited = true;
+ }
+
+ float s = curveTightness;
+ curveBasisMatrix.set((s-1)/2f, (s+3)/2f, (-3-s)/2f, (1-s)/2f,
+ (1-s), (-5-s)/2f, (s+2), (s-1)/2f,
+ (s-1)/2f, 0, (1-s)/2f, 0,
+ 0, 1, 0, 0);
+
+ //setup_spline_forward(segments, curveForwardMatrix);
+ splineForward(curveDetail, curveDrawMatrix);
+
+ if (bezierBasisInverse == null) {
+ bezierBasisInverse = bezierBasisMatrix.get();
+ bezierBasisInverse.invert();
+ curveToBezierMatrix = new PMatrix3D();
+ }
+
+ // TODO only needed for PGraphicsJava2D? if so, move it there
+ // actually, it's generally useful for other renderers, so keep it
+ // or hide the implementation elsewhere.
+ curveToBezierMatrix.set(curveBasisMatrix);
+ curveToBezierMatrix.preApply(bezierBasisInverse);
+
+ // multiply the basis and forward diff matrices together
+ // saves much time since this needn't be done for each curve
+ curveDrawMatrix.apply(curveBasisMatrix);
+ }
+
+
+ /**
+ * Draws a segment of Catmull-Rom curve.
+ *
+ * As of 0070, this function no longer doubles the first and
+ * last points. The curves are a bit more boring, but it's more
+ * mathematically correct, and properly mirrored in curvePoint().
+ *
+ * Identical to typing out:
+ * Given an (x, y, z) coordinate, returns the x position of where
+ * that point would be placed on screen, once affected by translate(),
+ * scale(), or any other transformations.
+ */
+ public float screenX(float x, float y, float z) {
+ showMissingWarning("screenX");
+ return 0;
+ }
+
+
+ /**
+ * Maps a three dimensional point to its placement on-screen.
+ *
+ * Given an (x, y, z) coordinate, returns the y position of where
+ * that point would be placed on screen, once affected by translate(),
+ * scale(), or any other transformations.
+ */
+ public float screenY(float x, float y, float z) {
+ showMissingWarning("screenY");
+ return 0;
+ }
+
+
+ /**
+ * Maps a three dimensional point to its placement on-screen.
+ *
+ * Given an (x, y, z) coordinate, returns its z value.
+ * This value can be used to determine if an (x, y, z) coordinate
+ * is in front or in back of another (x, y, z) coordinate.
+ * The units are based on how the zbuffer is set up, and don't
+ * relate to anything "real". They're only useful for in
+ * comparison to another value obtained from screenZ(),
+ * or directly out of the zbuffer[].
+ */
+ public float screenZ(float x, float y, float z) {
+ showMissingWarning("screenZ");
+ return 0;
+ }
+
+
+ /**
+ * Returns the model space x value for an x, y, z coordinate.
+ *
+ * This will give you a coordinate after it has been transformed
+ * by translate(), rotate(), and camera(), but not yet transformed
+ * by the projection matrix. For instance, his can be useful for
+ * figuring out how points in 3D space relate to the edge
+ * coordinates of a shape.
+ */
+ public float modelX(float x, float y, float z) {
+ showMissingWarning("modelX");
+ return 0;
+ }
+
+
+ /**
+ * Returns the model space y value for an x, y, z coordinate.
+ */
+ public float modelY(float x, float y, float z) {
+ showMissingWarning("modelY");
+ return 0;
+ }
+
+
+ /**
+ * Returns the model space z value for an x, y, z coordinate.
+ */
+ public float modelZ(float x, float y, float z) {
+ showMissingWarning("modelZ");
+ return 0;
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // STYLE
+
+
+ public void pushStyle() {
+ if (styleStackDepth == styleStack.length) {
+ styleStack = (PStyle[]) PApplet.expand(styleStack);
+ }
+ if (styleStack[styleStackDepth] == null) {
+ styleStack[styleStackDepth] = new PStyle();
+ }
+ PStyle s = styleStack[styleStackDepth++];
+ getStyle(s);
+ }
+
+
+ public void popStyle() {
+ if (styleStackDepth == 0) {
+ throw new RuntimeException("Too many popStyle() without enough pushStyle()");
+ }
+ styleStackDepth--;
+ style(styleStack[styleStackDepth]);
+ }
+
+
+ public void style(PStyle s) {
+ // if (s.smooth) {
+ // smooth();
+ // } else {
+ // noSmooth();
+ // }
+
+ imageMode(s.imageMode);
+ rectMode(s.rectMode);
+ ellipseMode(s.ellipseMode);
+ shapeMode(s.shapeMode);
+
+ if (s.tint) {
+ tint(s.tintColor);
+ } else {
+ noTint();
+ }
+ if (s.fill) {
+ fill(s.fillColor);
+ } else {
+ noFill();
+ }
+ if (s.stroke) {
+ stroke(s.strokeColor);
+ } else {
+ noStroke();
+ }
+ strokeWeight(s.strokeWeight);
+ strokeCap(s.strokeCap);
+ strokeJoin(s.strokeJoin);
+
+ // Set the colorMode() for the material properties.
+ // TODO this is really inefficient, need to just have a material() method,
+ // but this has the least impact to the API.
+ colorMode(RGB, 1);
+ ambient(s.ambientR, s.ambientG, s.ambientB);
+ emissive(s.emissiveR, s.emissiveG, s.emissiveB);
+ specular(s.specularR, s.specularG, s.specularB);
+ shininess(s.shininess);
+
+ /*
+ s.ambientR = ambientR;
+ s.ambientG = ambientG;
+ s.ambientB = ambientB;
+ s.specularR = specularR;
+ s.specularG = specularG;
+ s.specularB = specularB;
+ s.emissiveR = emissiveR;
+ s.emissiveG = emissiveG;
+ s.emissiveB = emissiveB;
+ s.shininess = shininess;
+ */
+ // material(s.ambientR, s.ambientG, s.ambientB,
+ // s.emissiveR, s.emissiveG, s.emissiveB,
+ // s.specularR, s.specularG, s.specularB,
+ // s.shininess);
+
+ // Set this after the material properties.
+ colorMode(s.colorMode,
+ s.colorModeX, s.colorModeY, s.colorModeZ, s.colorModeA);
+
+ // This is a bit asymmetric, since there's no way to do "noFont()",
+ // and a null textFont will produce an error (since usually that means that
+ // the font couldn't load properly). So in some cases, the font won't be
+ // 'cleared' to null, even though that's technically correct.
+ if (s.textFont != null) {
+ textFont(s.textFont, s.textSize);
+ textLeading(s.textLeading);
+ }
+ // These don't require a font to be set.
+ textAlign(s.textAlign, s.textAlignY);
+ textMode(s.textMode);
+ }
+
+
+ public PStyle getStyle() { // ignore
+ return getStyle(null);
+ }
+
+
+ public PStyle getStyle(PStyle s) { // ignore
+ if (s == null) {
+ s = new PStyle();
+ }
+
+ s.imageMode = imageMode;
+ s.rectMode = rectMode;
+ s.ellipseMode = ellipseMode;
+ s.shapeMode = shapeMode;
+
+ s.colorMode = colorMode;
+ s.colorModeX = colorModeX;
+ s.colorModeY = colorModeY;
+ s.colorModeZ = colorModeZ;
+ s.colorModeA = colorModeA;
+
+ s.tint = tint;
+ s.tintColor = tintColor;
+ s.fill = fill;
+ s.fillColor = fillColor;
+ s.stroke = stroke;
+ s.strokeColor = strokeColor;
+ s.strokeWeight = strokeWeight;
+ s.strokeCap = strokeCap;
+ s.strokeJoin = strokeJoin;
+
+ s.ambientR = ambientR;
+ s.ambientG = ambientG;
+ s.ambientB = ambientB;
+ s.specularR = specularR;
+ s.specularG = specularG;
+ s.specularB = specularB;
+ s.emissiveR = emissiveR;
+ s.emissiveG = emissiveG;
+ s.emissiveB = emissiveB;
+ s.shininess = shininess;
+
+ s.textFont = textFont;
+ s.textAlign = textAlign;
+ s.textAlignY = textAlignY;
+ s.textMode = textMode;
+ s.textSize = textSize;
+ s.textLeading = textLeading;
+
+ return s;
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // STROKE CAP/JOIN/WEIGHT
+
+
+ public void strokeWeight(float weight) {
+ strokeWeight = weight;
+ }
+
+
+ public void strokeJoin(int join) {
+ strokeJoin = join;
+ }
+
+
+ public void strokeCap(int cap) {
+ strokeCap = cap;
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // STROKE COLOR
+
+
+ public void noStroke() {
+ stroke = false;
+ }
+
+
+ /**
+ * Set the tint to either a grayscale or ARGB value.
+ * See notes attached to the fill() function.
+ */
+ public void stroke(int rgb) {
+// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { // see above
+// stroke((float) rgb);
+//
+// } else {
+// colorCalcARGB(rgb, colorModeA);
+// strokeFromCalc();
+// }
+ colorCalc(rgb);
+ strokeFromCalc();
+ }
+
+
+ public void stroke(int rgb, float alpha) {
+// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) {
+// stroke((float) rgb, alpha);
+//
+// } else {
+// colorCalcARGB(rgb, alpha);
+// strokeFromCalc();
+// }
+ colorCalc(rgb, alpha);
+ strokeFromCalc();
+ }
+
+
+ public void stroke(float gray) {
+ colorCalc(gray);
+ strokeFromCalc();
+ }
+
+
+ public void stroke(float gray, float alpha) {
+ colorCalc(gray, alpha);
+ strokeFromCalc();
+ }
+
+
+ public void stroke(float x, float y, float z) {
+ colorCalc(x, y, z);
+ strokeFromCalc();
+ }
+
+
+ public void stroke(float x, float y, float z, float a) {
+ colorCalc(x, y, z, a);
+ strokeFromCalc();
+ }
+
+
+ protected void strokeFromCalc() {
+ stroke = true;
+ strokeR = calcR;
+ strokeG = calcG;
+ strokeB = calcB;
+ strokeA = calcA;
+ strokeRi = calcRi;
+ strokeGi = calcGi;
+ strokeBi = calcBi;
+ strokeAi = calcAi;
+ strokeColor = calcColor;
+ strokeAlpha = calcAlpha;
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // TINT COLOR
+
+
+ public void noTint() {
+ tint = false;
+ }
+
+
+ /**
+ * Set the tint to either a grayscale or ARGB value.
+ */
+ public void tint(int rgb) {
+// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) {
+// tint((float) rgb);
+//
+// } else {
+// colorCalcARGB(rgb, colorModeA);
+// tintFromCalc();
+// }
+ colorCalc(rgb);
+ tintFromCalc();
+ }
+
+ public void tint(int rgb, float alpha) {
+// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) {
+// tint((float) rgb, alpha);
+//
+// } else {
+// colorCalcARGB(rgb, alpha);
+// tintFromCalc();
+// }
+ colorCalc(rgb, alpha);
+ tintFromCalc();
+ }
+
+ public void tint(float gray) {
+ colorCalc(gray);
+ tintFromCalc();
+ }
+
+
+ public void tint(float gray, float alpha) {
+ colorCalc(gray, alpha);
+ tintFromCalc();
+ }
+
+
+ public void tint(float x, float y, float z) {
+ colorCalc(x, y, z);
+ tintFromCalc();
+ }
+
+
+ public void tint(float x, float y, float z, float a) {
+ colorCalc(x, y, z, a);
+ tintFromCalc();
+ }
+
+
+ protected void tintFromCalc() {
+ tint = true;
+ tintR = calcR;
+ tintG = calcG;
+ tintB = calcB;
+ tintA = calcA;
+ tintRi = calcRi;
+ tintGi = calcGi;
+ tintBi = calcBi;
+ tintAi = calcAi;
+ tintColor = calcColor;
+ tintAlpha = calcAlpha;
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // FILL COLOR
+
+
+ public void noFill() {
+ fill = false;
+ }
+
+
+ /**
+ * Set the fill to either a grayscale value or an ARGB int.
+ */
+ public void fill(int rgb) {
+// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { // see above
+// fill((float) rgb);
+//
+// } else {
+// colorCalcARGB(rgb, colorModeA);
+// fillFromCalc();
+// }
+ colorCalc(rgb);
+ fillFromCalc();
+ }
+
+
+ public void fill(int rgb, float alpha) {
+// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { // see above
+// fill((float) rgb, alpha);
+//
+// } else {
+// colorCalcARGB(rgb, alpha);
+// fillFromCalc();
+// }
+ colorCalc(rgb, alpha);
+ fillFromCalc();
+ }
+
+
+ public void fill(float gray) {
+ colorCalc(gray);
+ fillFromCalc();
+ }
+
+
+ public void fill(float gray, float alpha) {
+ colorCalc(gray, alpha);
+ fillFromCalc();
+ }
+
+
+ public void fill(float x, float y, float z) {
+ colorCalc(x, y, z);
+ fillFromCalc();
+ }
+
+
+ public void fill(float x, float y, float z, float a) {
+ colorCalc(x, y, z, a);
+ fillFromCalc();
+ }
+
+
+ protected void fillFromCalc() {
+ fill = true;
+ fillR = calcR;
+ fillG = calcG;
+ fillB = calcB;
+ fillA = calcA;
+ fillRi = calcRi;
+ fillGi = calcGi;
+ fillBi = calcBi;
+ fillAi = calcAi;
+ fillColor = calcColor;
+ fillAlpha = calcAlpha;
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // MATERIAL PROPERTIES
+
+
+ public void ambient(int rgb) {
+// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) {
+// ambient((float) rgb);
+//
+// } else {
+// colorCalcARGB(rgb, colorModeA);
+// ambientFromCalc();
+// }
+ colorCalc(rgb);
+ ambientFromCalc();
+ }
+
+
+ public void ambient(float gray) {
+ colorCalc(gray);
+ ambientFromCalc();
+ }
+
+
+ public void ambient(float x, float y, float z) {
+ colorCalc(x, y, z);
+ ambientFromCalc();
+ }
+
+
+ protected void ambientFromCalc() {
+ ambientR = calcR;
+ ambientG = calcG;
+ ambientB = calcB;
+ }
+
+
+ public void specular(int rgb) {
+// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) {
+// specular((float) rgb);
+//
+// } else {
+// colorCalcARGB(rgb, colorModeA);
+// specularFromCalc();
+// }
+ colorCalc(rgb);
+ specularFromCalc();
+ }
+
+
+ public void specular(float gray) {
+ colorCalc(gray);
+ specularFromCalc();
+ }
+
+
+ public void specular(float x, float y, float z) {
+ colorCalc(x, y, z);
+ specularFromCalc();
+ }
+
+
+ protected void specularFromCalc() {
+ specularR = calcR;
+ specularG = calcG;
+ specularB = calcB;
+ }
+
+
+ public void shininess(float shine) {
+ shininess = shine;
+ }
+
+
+ public void emissive(int rgb) {
+// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) {
+// emissive((float) rgb);
+//
+// } else {
+// colorCalcARGB(rgb, colorModeA);
+// emissiveFromCalc();
+// }
+ colorCalc(rgb);
+ emissiveFromCalc();
+ }
+
+
+ public void emissive(float gray) {
+ colorCalc(gray);
+ emissiveFromCalc();
+ }
+
+
+ public void emissive(float x, float y, float z) {
+ colorCalc(x, y, z);
+ emissiveFromCalc();
+ }
+
+
+ protected void emissiveFromCalc() {
+ emissiveR = calcR;
+ emissiveG = calcG;
+ emissiveB = calcB;
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // LIGHTS
+
+ // The details of lighting are very implementation-specific, so this base
+ // class does not handle any details of settings lights. It does however
+ // display warning messages that the functions are not available.
+
+
+ public void lights() {
+ showMethodWarning("lights");
+ }
+
+ public void noLights() {
+ showMethodWarning("noLights");
+ }
+
+ public void ambientLight(float red, float green, float blue) {
+ showMethodWarning("ambientLight");
+ }
+
+ public void ambientLight(float red, float green, float blue,
+ float x, float y, float z) {
+ showMethodWarning("ambientLight");
+ }
+
+ public void directionalLight(float red, float green, float blue,
+ float nx, float ny, float nz) {
+ showMethodWarning("directionalLight");
+ }
+
+ public void pointLight(float red, float green, float blue,
+ float x, float y, float z) {
+ showMethodWarning("pointLight");
+ }
+
+ public void spotLight(float red, float green, float blue,
+ float x, float y, float z,
+ float nx, float ny, float nz,
+ float angle, float concentration) {
+ showMethodWarning("spotLight");
+ }
+
+ public void lightFalloff(float constant, float linear, float quadratic) {
+ showMethodWarning("lightFalloff");
+ }
+
+ public void lightSpecular(float x, float y, float z) {
+ showMethodWarning("lightSpecular");
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // BACKGROUND
+
+ /**
+ * Set the background to a gray or ARGB color.
+ *
+ * For the main drawing surface, the alpha value will be ignored. However,
+ * alpha can be used on PGraphics objects from createGraphics(). This is
+ * the only way to set all the pixels partially transparent, for instance.
+ *
+ * Note that background() should be called before any transformations occur,
+ * because some implementations may require the current transformation matrix
+ * to be identity before drawing.
+ */
+ public void background(int rgb) {
+// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) {
+// background((float) rgb);
+//
+// } else {
+// if (format == RGB) {
+// rgb |= 0xff000000; // ignore alpha for main drawing surface
+// }
+// colorCalcARGB(rgb, colorModeA);
+// backgroundFromCalc();
+// backgroundImpl();
+// }
+ colorCalc(rgb);
+ backgroundFromCalc();
+ }
+
+
+ /**
+ * See notes about alpha in background(x, y, z, a).
+ */
+ public void background(int rgb, float alpha) {
+// if (format == RGB) {
+// background(rgb); // ignore alpha for main drawing surface
+//
+// } else {
+// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) {
+// background((float) rgb, alpha);
+//
+// } else {
+// colorCalcARGB(rgb, alpha);
+// backgroundFromCalc();
+// backgroundImpl();
+// }
+// }
+ colorCalc(rgb, alpha);
+ backgroundFromCalc();
+ }
+
+
+ /**
+ * Set the background to a grayscale value, based on the
+ * current colorMode.
+ */
+ public void background(float gray) {
+ colorCalc(gray);
+ backgroundFromCalc();
+// backgroundImpl();
+ }
+
+
+ /**
+ * See notes about alpha in background(x, y, z, a).
+ */
+ public void background(float gray, float alpha) {
+ if (format == RGB) {
+ background(gray); // ignore alpha for main drawing surface
+
+ } else {
+ colorCalc(gray, alpha);
+ backgroundFromCalc();
+// backgroundImpl();
+ }
+ }
+
+
+ /**
+ * Set the background to an r, g, b or h, s, b value,
+ * based on the current colorMode.
+ */
+ public void background(float x, float y, float z) {
+ colorCalc(x, y, z);
+ backgroundFromCalc();
+// backgroundImpl();
+ }
+
+
+ /**
+ * Clear the background with a color that includes an alpha value. This can
+ * only be used with objects created by createGraphics(), because the main
+ * drawing surface cannot be set transparent.
+ *
+ * It might be tempting to use this function to partially clear the screen
+ * on each frame, however that's not how this function works. When calling
+ * background(), the pixels will be replaced with pixels that have that level
+ * of transparency. To do a semi-transparent overlay, use fill() with alpha
+ * and draw a rectangle.
+ */
+ public void background(float x, float y, float z, float a) {
+// if (format == RGB) {
+// background(x, y, z); // don't allow people to set alpha
+//
+// } else {
+// colorCalc(x, y, z, a);
+// backgroundFromCalc();
+// backgroundImpl();
+// }
+ colorCalc(x, y, z, a);
+ backgroundFromCalc();
+ }
+
+
+ protected void backgroundFromCalc() {
+ backgroundR = calcR;
+ backgroundG = calcG;
+ backgroundB = calcB;
+ backgroundA = (format == RGB) ? colorModeA : calcA;
+ backgroundRi = calcRi;
+ backgroundGi = calcGi;
+ backgroundBi = calcBi;
+ backgroundAi = (format == RGB) ? 255 : calcAi;
+ backgroundAlpha = (format == RGB) ? false : calcAlpha;
+ backgroundColor = calcColor;
+
+ backgroundImpl();
+ }
+
+
+ /**
+ * Takes an RGB or ARGB image and sets it as the background.
+ * The width and height of the image must be the same size as the sketch.
+ * Use image.resize(width, height) to make short work of such a task.
+ *
+ * Note that even if the image is set as RGB, the high 8 bits of each pixel
+ * should be set opaque (0xFF000000), because the image data will be copied
+ * directly to the screen, and non-opaque background images may have strange
+ * behavior. Using image.filter(OPAQUE) will handle this easily.
+ *
+ * When using 3D, this will also clear the zbuffer (if it exists).
+ */
+ public void background(PImage image) {
+ if ((image.width != width) || (image.height != height)) {
+ throw new RuntimeException(ERROR_BACKGROUND_IMAGE_SIZE);
+ }
+ if ((image.format != RGB) && (image.format != ARGB)) {
+ throw new RuntimeException(ERROR_BACKGROUND_IMAGE_FORMAT);
+ }
+ backgroundColor = 0; // just zero it out for images
+ backgroundImpl(image);
+ }
+
+
+ /**
+ * Actually set the background image. This is separated from the error
+ * handling and other semantic goofiness that is shared across renderers.
+ */
+ protected void backgroundImpl(PImage image) {
+ // blit image to the screen
+ set(0, 0, image);
+ }
+
+
+ /**
+ * Actual implementation of clearing the background, now that the
+ * internal variables for background color have been set. Called by the
+ * backgroundFromCalc() method, which is what all the other background()
+ * methods call once the work is done.
+ */
+ protected void backgroundImpl() {
+ pushStyle();
+ pushMatrix();
+ resetMatrix();
+ fill(backgroundColor);
+ rect(0, 0, width, height);
+ popMatrix();
+ popStyle();
+ }
+
+
+ /**
+ * Callback to handle clearing the background when begin/endRaw is in use.
+ * Handled as separate function for OpenGL (or other) subclasses that
+ * override backgroundImpl() but still needs this to work properly.
+ */
+// protected void backgroundRawImpl() {
+// if (raw != null) {
+// raw.colorMode(RGB, 1);
+// raw.noStroke();
+// raw.fill(backgroundR, backgroundG, backgroundB);
+// raw.beginShape(TRIANGLES);
+//
+// raw.vertex(0, 0);
+// raw.vertex(width, 0);
+// raw.vertex(0, height);
+//
+// raw.vertex(width, 0);
+// raw.vertex(width, height);
+// raw.vertex(0, height);
+//
+// raw.endShape();
+// }
+// }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // COLOR MODE
+
+
+ public void colorMode(int mode) {
+ colorMode(mode, colorModeX, colorModeY, colorModeZ, colorModeA);
+ }
+
+
+ public void colorMode(int mode, float max) {
+ colorMode(mode, max, max, max, max);
+ }
+
+
+ /**
+ * Set the colorMode and the maximum values for (r, g, b)
+ * or (h, s, b).
+ *
+ * Note that this doesn't set the maximum for the alpha value,
+ * which might be confusing if for instance you switched to
+ *
+ * The problem with this code is that it has to detect between these two
+ * situations automatically. This is done by checking to see if the high bits
+ * (the alpha for 0xAA000000) is set, and if not, whether the color value
+ * that follows is less than colorModeX (first param passed to colorMode).
+ *
+ * This auto-detect would break in the following situation:
+ *
+ * Handled here with its own function since this is indepenent
+ * of the color mode.
+ *
+ * Strangely the old version of this code ignored the alpha
+ * value. not sure if that was a bug or what.
+ *
+ * Note, no need for a bounds check since it's a 32 bit number.
+ */
+ protected void colorCalcARGB(int argb, float alpha) {
+ if (alpha == colorModeA) {
+ calcAi = (argb >> 24) & 0xff;
+ calcColor = argb;
+ } else {
+ calcAi = (int) (((argb >> 24) & 0xff) * (alpha / colorModeA));
+ calcColor = (calcAi << 24) | (argb & 0xFFFFFF);
+ }
+ calcRi = (argb >> 16) & 0xff;
+ calcGi = (argb >> 8) & 0xff;
+ calcBi = argb & 0xff;
+ calcA = (float)calcAi / 255.0f;
+ calcR = (float)calcRi / 255.0f;
+ calcG = (float)calcGi / 255.0f;
+ calcB = (float)calcBi / 255.0f;
+ calcAlpha = (calcAi != 255);
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // COLOR DATATYPE STUFFING
+
+ // The 'color' primitive type in Processing syntax is in fact a 32-bit int.
+ // These functions handle stuffing color values into a 32-bit cage based
+ // on the current colorMode settings.
+
+ // These functions are really slow (because they take the current colorMode
+ // into account), but they're easy to use. Advanced users can write their
+ // own bit shifting operations to setup 'color' data types.
+
+
+ public final int color(int gray) { // ignore
+ if (((gray & 0xff000000) == 0) && (gray <= colorModeX)) {
+ if (colorModeDefault) {
+ // bounds checking to make sure the numbers aren't to high or low
+ if (gray > 255) gray = 255; else if (gray < 0) gray = 0;
+ return 0xff000000 | (gray << 16) | (gray << 8) | gray;
+ } else {
+ colorCalc(gray);
+ }
+ } else {
+ colorCalcARGB(gray, colorModeA);
+ }
+ return calcColor;
+ }
+
+
+ public final int color(float gray) { // ignore
+ colorCalc(gray);
+ return calcColor;
+ }
+
+
+ /**
+ * @param gray can be packed ARGB or a gray in this case
+ */
+ public final int color(int gray, int alpha) { // ignore
+ if (colorModeDefault) {
+ // bounds checking to make sure the numbers aren't to high or low
+ if (gray > 255) gray = 255; else if (gray < 0) gray = 0;
+ if (alpha > 255) alpha = 255; else if (alpha < 0) alpha = 0;
+
+ return ((alpha & 0xff) << 24) | (gray << 16) | (gray << 8) | gray;
+ }
+ colorCalc(gray, alpha);
+ return calcColor;
+ }
+
+
+ /**
+ * @param rgb can be packed ARGB or a gray in this case
+ */
+ public final int color(int rgb, float alpha) { // ignore
+ if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) {
+ colorCalc(rgb, alpha);
+ } else {
+ colorCalcARGB(rgb, alpha);
+ }
+ return calcColor;
+ }
+
+
+ public final int color(float gray, float alpha) { // ignore
+ colorCalc(gray, alpha);
+ return calcColor;
+ }
+
+
+ public final int color(int x, int y, int z) { // ignore
+ if (colorModeDefault) {
+ // bounds checking to make sure the numbers aren't to high or low
+ if (x > 255) x = 255; else if (x < 0) x = 0;
+ if (y > 255) y = 255; else if (y < 0) y = 0;
+ if (z > 255) z = 255; else if (z < 0) z = 0;
+
+ return 0xff000000 | (x << 16) | (y << 8) | z;
+ }
+ colorCalc(x, y, z);
+ return calcColor;
+ }
+
+
+ public final int color(float x, float y, float z) { // ignore
+ colorCalc(x, y, z);
+ return calcColor;
+ }
+
+
+ public final int color(int x, int y, int z, int a) { // ignore
+ if (colorModeDefault) {
+ // bounds checking to make sure the numbers aren't to high or low
+ if (a > 255) a = 255; else if (a < 0) a = 0;
+ if (x > 255) x = 255; else if (x < 0) x = 0;
+ if (y > 255) y = 255; else if (y < 0) y = 0;
+ if (z > 255) z = 255; else if (z < 0) z = 0;
+
+ return (a << 24) | (x << 16) | (y << 8) | z;
+ }
+ colorCalc(x, y, z, a);
+ return calcColor;
+ }
+
+
+ public final int color(float x, float y, float z, float a) { // ignore
+ colorCalc(x, y, z, a);
+ return calcColor;
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // COLOR DATATYPE EXTRACTION
+
+ // Vee have veys of making the colors talk.
+
+
+ public final float alpha(int what) {
+ float c = (what >> 24) & 0xff;
+ if (colorModeA == 255) return c;
+ return (c / 255.0f) * colorModeA;
+ }
+
+
+ public final float red(int what) {
+ float c = (what >> 16) & 0xff;
+ if (colorModeDefault) return c;
+ return (c / 255.0f) * colorModeX;
+ }
+
+
+ public final float green(int what) {
+ float c = (what >> 8) & 0xff;
+ if (colorModeDefault) return c;
+ return (c / 255.0f) * colorModeY;
+ }
+
+
+ public final float blue(int what) {
+ float c = (what) & 0xff;
+ if (colorModeDefault) return c;
+ return (c / 255.0f) * colorModeZ;
+ }
+
+
+ public final float hue(int what) {
+ if (what != cacheHsbKey) {
+ Color.RGBtoHSB((what >> 16) & 0xff, (what >> 8) & 0xff,
+ what & 0xff, cacheHsbValue);
+ cacheHsbKey = what;
+ }
+ return cacheHsbValue[0] * colorModeX;
+ }
+
+
+ public final float saturation(int what) {
+ if (what != cacheHsbKey) {
+ Color.RGBtoHSB((what >> 16) & 0xff, (what >> 8) & 0xff,
+ what & 0xff, cacheHsbValue);
+ cacheHsbKey = what;
+ }
+ return cacheHsbValue[1] * colorModeY;
+ }
+
+
+ public final float brightness(int what) {
+ if (what != cacheHsbKey) {
+ Color.RGBtoHSB((what >> 16) & 0xff, (what >> 8) & 0xff,
+ what & 0xff, cacheHsbValue);
+ cacheHsbKey = what;
+ }
+ return cacheHsbValue[2] * colorModeZ;
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // COLOR DATATYPE INTERPOLATION
+
+ // Against our better judgement.
+
+
+ /**
+ * Interpolate between two colors, using the current color mode.
+ */
+ public int lerpColor(int c1, int c2, float amt) {
+ return lerpColor(c1, c2, amt, colorMode);
+ }
+
+ static float[] lerpColorHSB1;
+ static float[] lerpColorHSB2;
+
+ /**
+ * Interpolate between two colors. Like lerp(), but for the
+ * individual color components of a color supplied as an int value.
+ */
+ static public int lerpColor(int c1, int c2, float amt, int mode) {
+ if (mode == RGB) {
+ float a1 = ((c1 >> 24) & 0xff);
+ float r1 = (c1 >> 16) & 0xff;
+ float g1 = (c1 >> 8) & 0xff;
+ float b1 = c1 & 0xff;
+ float a2 = (c2 >> 24) & 0xff;
+ float r2 = (c2 >> 16) & 0xff;
+ float g2 = (c2 >> 8) & 0xff;
+ float b2 = c2 & 0xff;
+
+ return (((int) (a1 + (a2-a1)*amt) << 24) |
+ ((int) (r1 + (r2-r1)*amt) << 16) |
+ ((int) (g1 + (g2-g1)*amt) << 8) |
+ ((int) (b1 + (b2-b1)*amt)));
+
+ } else if (mode == HSB) {
+ if (lerpColorHSB1 == null) {
+ lerpColorHSB1 = new float[3];
+ lerpColorHSB2 = new float[3];
+ }
+
+ float a1 = (c1 >> 24) & 0xff;
+ float a2 = (c2 >> 24) & 0xff;
+ int alfa = ((int) (a1 + (a2-a1)*amt)) << 24;
+
+ Color.RGBtoHSB((c1 >> 16) & 0xff, (c1 >> 8) & 0xff, c1 & 0xff,
+ lerpColorHSB1);
+ Color.RGBtoHSB((c2 >> 16) & 0xff, (c2 >> 8) & 0xff, c2 & 0xff,
+ lerpColorHSB2);
+
+ /* If mode is HSB, this will take the shortest path around the
+ * color wheel to find the new color. For instance, red to blue
+ * will go red violet blue (backwards in hue space) rather than
+ * cycling through ROYGBIV.
+ */
+ // Disabling rollover (wasn't working anyway) for 0126.
+ // Otherwise it makes full spectrum scale impossible for
+ // those who might want it...in spite of how despicable
+ // a full spectrum scale might be.
+ // roll around when 0.9 to 0.1
+ // more than 0.5 away means that it should roll in the other direction
+ /*
+ float h1 = lerpColorHSB1[0];
+ float h2 = lerpColorHSB2[0];
+ if (Math.abs(h1 - h2) > 0.5f) {
+ if (h1 > h2) {
+ // i.e. h1 is 0.7, h2 is 0.1
+ h2 += 1;
+ } else {
+ // i.e. h1 is 0.1, h2 is 0.7
+ h1 += 1;
+ }
+ }
+ float ho = (PApplet.lerp(lerpColorHSB1[0], lerpColorHSB2[0], amt)) % 1.0f;
+ */
+ float ho = PApplet.lerp(lerpColorHSB1[0], lerpColorHSB2[0], amt);
+ float so = PApplet.lerp(lerpColorHSB1[1], lerpColorHSB2[1], amt);
+ float bo = PApplet.lerp(lerpColorHSB1[2], lerpColorHSB2[2], amt);
+
+ return alfa | (Color.HSBtoRGB(ho, so, bo) & 0xFFFFFF);
+ }
+ return 0;
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // BEGINRAW/ENDRAW
+
+
+ /**
+ * Record individual lines and triangles by echoing them to another renderer.
+ */
+ public void beginRaw(PGraphics rawGraphics) { // ignore
+ this.raw = rawGraphics;
+ rawGraphics.beginDraw();
+ }
+
+
+ public void endRaw() { // ignore
+ if (raw != null) {
+ // for 3D, need to flush any geometry that's been stored for sorting
+ // (particularly if the ENABLE_DEPTH_SORT hint is set)
+ flush();
+
+ // just like beginDraw, this will have to be called because
+ // endDraw() will be happening outside of draw()
+ raw.endDraw();
+ raw.dispose();
+ raw = null;
+ }
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // WARNINGS and EXCEPTIONS
+
+
+ static protected HashMap
+ * Code for copying, resizing, scaling, and blending contributed
+ * by toxi.
+ *
+ */
+public class PImage implements PConstants, Cloneable {
+
+ /**
+ * Format for this image, one of RGB, ARGB or ALPHA.
+ * note that RGB images still require 0xff in the high byte
+ * because of how they'll be manipulated by other functions
+ */
+ public int format;
+
+ public int[] pixels;
+ public int width, height;
+
+ /**
+ * Path to parent object that will be used with save().
+ * This prevents users from needing savePath() to use PImage.save().
+ */
+ public PApplet parent;
+
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
+ /** for subclasses that need to store info about the image */
+ protected HashMap
+ * As of revision 0100, this function requires an absolute path,
+ * in order to avoid confusion. To save inside the sketch folder,
+ * use the function savePath() from PApplet, or use saveFrame() instead.
+ * As of revision 0116, savePath() is not needed if this object has been
+ * created (as recommended) via createImage() or createGraphics() or
+ * one of its neighbors.
+ *
+ * As of revision 0115, when using Java 1.4 and later, you can write
+ * to several formats besides tga and tiff. If Java 1.4 is installed
+ * and the extension used is supported (usually png, jpg, jpeg, bmp,
+ * and tiff), then those methods will be used to write the image.
+ * To get a list of the supported formats for writing, use:
+ * To use the original built-in image writers, use .tga or .tif as the
+ * extension, or don't include an extension. When no extension is used,
+ * the extension .tif will be added to the file name.
+ *
+ * The ImageIO API claims to support wbmp files, however they probably
+ * require a black and white image. Basic testing produced a zero-length
+ * file with no error.
+ */
+ public void save(String path) { // ignore
+ boolean success = false;
+
+ File file = new File(path);
+ if (!file.isAbsolute()) {
+ if (parent != null) {
+ //file = new File(parent.savePath(filename));
+ path = parent.savePath(path);
+ } else {
+ String msg = "PImage.save() requires an absolute path. " +
+ "Use createImage(), or pass savePath() to save().";
+ PGraphics.showException(msg);
+ }
+ }
+
+ // Make sure the pixel data is ready to go
+ loadPixels();
+
+ try {
+ OutputStream os = null;
+
+ if (saveImageFormats == null) {
+ saveImageFormats = javax.imageio.ImageIO.getWriterFormatNames();
+ }
+ if (saveImageFormats != null) {
+ for (int i = 0; i < saveImageFormats.length; i++) {
+ if (path.endsWith("." + saveImageFormats[i])) {
+ saveImageIO(path);
+ return;
+ }
+ }
+ }
+
+ if (path.toLowerCase().endsWith(".tga")) {
+ os = new BufferedOutputStream(new FileOutputStream(path), 32768);
+ success = saveTGA(os); //, pixels, width, height, format);
+
+ } else {
+ if (!path.toLowerCase().endsWith(".tif") &&
+ !path.toLowerCase().endsWith(".tiff")) {
+ // if no .tif extension, add it..
+ path += ".tif";
+ }
+ os = new BufferedOutputStream(new FileOutputStream(path), 32768);
+ success = saveTIFF(os); //, pixels, width, height);
+ }
+ os.flush();
+ os.close();
+
+ } catch (IOException e) {
+ //System.err.println("Error while saving image.");
+ e.printStackTrace();
+ success = false;
+ }
+ if (!success) {
+ throw new RuntimeException("Error while saving image.");
+ }
+ }
+}
+
diff --git a/core/preproc/src/processing/build/PAppletMethods.java b/core/preproc/src/processing/build/PAppletMethods.java
new file mode 100644
index 000000000..ed27b2f8c
--- /dev/null
+++ b/core/preproc/src/processing/build/PAppletMethods.java
@@ -0,0 +1,236 @@
+package processing.build;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+
+
+/**
+ * Ant Task for copying the PImage and PGraphics methods into PApplet.
+ */
+public class PAppletMethods extends Task {
+
+ private File baseDir;
+
+
+ public PAppletMethods() {
+ }
+
+
+ public void setDir(String dir) {
+ baseDir = new File(dir);
+ }
+
+
+ public void execute() throws BuildException {
+ // Do a bunch of checks...
+ if (baseDir == null) {
+ throw new BuildException("dir parameter must be set!");
+ }
+
+ File graphicsFile = new File(baseDir, "PGraphics.java");
+ File appletFile = new File(baseDir, "PApplet.java");
+ File imageFile = new File(baseDir, "PImage.java");
+
+ if (!graphicsFile.exists() || !graphicsFile.canRead()) {
+ throw new BuildException("PGraphics file not readable: " +
+ graphicsFile.getAbsolutePath());
+ }
+
+ if (!appletFile.exists() || !appletFile.canRead() || !appletFile.canWrite()) {
+ throw new BuildException("PApplet file not read/writeable: " +
+ appletFile.getAbsolutePath());
+ }
+
+ if (!imageFile.exists() || !imageFile.canRead()) {
+ throw new BuildException("PImage file not readable: " +
+ imageFile.getAbsolutePath());
+ }
+
+ // Looking good, let's do this!
+ ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
+ PrintStream out = new PrintStream(outBytes);
+ StringBuffer content = new StringBuffer();
+
+ try{
+ BufferedReader applet = createReader(appletFile);
+ String line;
+ while ((line = applet.readLine()) != null) {
+ out.println(line);
+ content.append(line + "\n");
+
+ if (line.indexOf("public functions for processing.core") >= 0) {
+ break;
+ }
+ }
+
+ // read the rest of the file and append it to the
+ while ((line = applet.readLine()) != null) {
+ content.append(line + "\n");
+ }
+
+ applet.close();
+ process(out, graphicsFile);
+ process(out, imageFile);
+
+ out.println("}");
+
+ } catch (IOException e) {
+ e.printStackTrace();
+
+ } catch(Exception ex) {
+ ex.printStackTrace();
+ }
+ out.flush();
+
+ if (content.toString().equals(outBytes.toString())) {
+ System.out.println("No changes to PApplet API.");
+ } else {
+ System.out.println("Updating PApplet with API changes from PImage or PGraphics.");
+ try {
+ PrintStream temp = new PrintStream(appletFile);
+ temp.print(outBytes.toString());
+ temp.flush();
+ temp.close();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+
+ private void process(PrintStream out, File input) throws IOException {
+ BufferedReader in = createReader(input);
+ int comments = 0;
+ String line = null;
+
+ while ((line = in.readLine()) != null) {
+ String decl = "";
+
+ // Keep track of comments
+ if (line.matches(Pattern.quote("/*"))) {
+ comments ++;
+ }
+
+ if (line.matches(Pattern.quote("*/"))) {
+ comments --;
+ }
+
+ // Ignore everything inside comments
+ if (comments > 0) {
+ continue;
+ }
+
+ boolean gotSomething = false;
+ boolean gotStatic = false;
+
+ Matcher result;
+
+ if ((result = Pattern.compile("^\\s*public ([\\w\\[\\]]+) [a-zA-z_]+\\(.*$").matcher(line)).matches()) {
+ gotSomething = true;
+ }
+ else if ((result = Pattern.compile("^\\s*abstract public ([\\w\\[\\]]+) [a-zA-z_]+\\(.*$").matcher(line)).matches()) {
+ gotSomething = true;
+ }
+ else if ((result = Pattern.compile("^\\s*public final ([\\w\\[\\]]+) [a-zA-z_]+\\(.*$").matcher(line)).matches()) {
+ gotSomething = true;
+ }
+ else if ((result = Pattern.compile("^\\s*static public ([\\w\\[\\]]+) [a-zA-z_]+\\(.*$").matcher(line)).matches()) {
+ gotSomething = true;
+ gotStatic = true;
+ }
+
+ // if function is marked "// ignore" then, uh, ignore it.
+ if (gotSomething && line.indexOf("// ignore") >= 0) {
+ gotSomething = false;
+ }
+
+ String returns = "";
+ if (gotSomething) {
+ if (result.group(1).equals("void")) {
+ returns = "";
+ } else {
+ returns = "return ";
+ }
+
+ // remove the abstract modifier
+ line = line.replaceFirst(Pattern.quote("abstract"), " ");
+
+ // replace semicolons with a start def
+ line = line.replaceAll(Pattern.quote(";"), " {\n");
+
+ out.println("\n\n" + line);
+
+ decl += line;
+ while(line.indexOf(')') == -1) {
+ line = in.readLine();
+ decl += line;
+ line = line.replaceAll("\\;\\s*$", " {\n");
+ out.println(line);
+ }
+
+ result = Pattern.compile(".*?\\s(\\S+)\\(.*?").matcher(decl);
+ result.matches(); // try to match. DON't remove this or things will stop working!
+ String declName = result.group(1);
+ String gline = "";
+ String rline = "";
+ if (gotStatic) {
+ gline = " " + returns + "PGraphics." + declName + "(";
+ } else {
+ rline = " if (recorder != null) recorder." + declName + "(";
+ gline = " " + returns + "g." + declName + "(";
+ }
+
+ decl = decl.replaceAll("\\s+", " "); // smush onto a single line
+ decl = decl.replaceFirst("^.*\\(", "");
+ decl = decl.replaceFirst("\\).*$", "");
+
+ int prev = 0;
+ String parts[] = decl.split("\\, ");
+
+ for (String part : parts) {
+ if (!part.trim().equals("")) {
+ String blargh[] = part.split(" ");
+ String theArg = blargh[1].replaceAll("[\\[\\]]", "");
+
+ if (prev != 0) {
+ gline += ", ";
+ rline += ", ";
+ }
+
+ gline += theArg;
+ rline += theArg;
+ prev = 1;
+ }
+ }
+
+ gline += ");";
+ rline += ");";
+
+ if (!gotStatic && returns.equals("")) {
+ out.println(rline);
+ }
+
+ out.println(gline);
+ out.println(" }");
+ }
+ }
+
+ in.close();
+ }
+
+
+ private static BufferedReader createReader(File f) throws FileNotFoundException {
+ return new BufferedReader(new InputStreamReader(new FileInputStream(f)));
+ }
+}
+ *
+ * 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 Subclassing and initializing PGraphics objects
+ * Starting in release 0149, subclasses of PGraphics are handled differently.
+ * The constructor for subclasses takes no parameters, instead a series of
+ * functions are called by the hosting PApplet to specify its attributes.
+ *
+ *
+ * The functions were broken out because of the growing number of parameters
+ * such as these that might be used by a renderer, yet with the exception of
+ * setSize(), it's not clear which will be necessary. So while the size could
+ * be passed in to the constructor instead of a setSize() function, a function
+ * would still be needed that would notify the renderer that it was time to
+ * finish its initialization. Thus, setSize() simply does both.
+ *
+ * Know your rights: public vs. private methods
+ * Methods that are protected are often subclassed by other renderers, however
+ * they are not set 'public' because they shouldn't be part of the user-facing
+ * public API accessible from PApplet. That is, we don't want sketches calling
+ * textModeCheck() or vertexTexture() directly.
+ *
+ * Handling warnings and exceptions
+ * Methods that are unavailable generally show a warning, unless their lack of
+ * availability will soon cause another exception. For instance, if a method
+ * like getMatrix() returns null because it is unavailable, an exception will
+ * be thrown stating that the method is unavailable, rather than waiting for
+ * the NullPointerException that will occur when the sketch tries to use that
+ * method. As of release 0149, warnings will only be shown once, and exceptions
+ * have been changed to warnings where possible.
+ *
+ * Using xxxxImpl() for subclassing smoothness
+ * The xxxImpl() methods are generally renderer-specific handling for some
+ * subset if tasks for a particular function (vague enough for you?) For
+ * instance, imageImpl() handles drawing an image whose x/y/w/h and u/v coords
+ * have been specified, and screen placement (independent of imageMode) has
+ * been determined. There's no point in all renderers implementing the
+ * if (imageMode == BLAH) placement/sizing logic, so that's handled
+ * by PGraphics, which then calls imageImpl() once all that is figured out.
+ *
+ * His brother PImage
+ * PGraphics subclasses PImage so that it can be drawn and manipulated in a
+ * similar fashion. As such, many methods are inherited from PGraphics,
+ * though many are unavailable: for instance, resize() is not likely to be
+ * implemented; the same goes for mask(), depending on the situation.
+ *
+ * What's in PGraphics, what ain't
+ * For the benefit of subclasses, as much as possible has been placed inside
+ * PGraphics. For instance, bezier interpolation code and implementations of
+ * the strokeCap() method (that simply sets the strokeCap variable) are
+ * handled here. Features that will vary widely between renderers are located
+ * inside the subclasses themselves. For instance, all matrix handling code
+ * is per-renderer: Java 2D uses its own AffineTransform, P2D uses a PMatrix2D,
+ * and PGraphics3D needs to keep continually update forward and reverse
+ * transformations. A proper (future) OpenGL implementation will have all its
+ * matrix madness handled by the card. Lighting also falls under this
+ * category, however the base material property settings (emissive, specular,
+ * et al.) are handled in PGraphics because they use the standard colorMode()
+ * logic. Subclasses should override methods like emissiveFromCalc(), which
+ * is a point where a valid color has been defined internally, and can be
+ * applied in some manner based on the calcXxxx values.
+ *
+ * What's in the PGraphics documentation, what ain't
+ * Some things are noted here, some things are not. For public API, always
+ * refer to the reference
+ * on Processing.org for proper explanations. No attempt has been made to
+ * keep the javadoc up to date or complete. It's an enormous task for
+ * which we simply do not have the time. That is, it's not something that
+ * to be done once—it's a matter of keeping the multiple references
+ * synchronized (to say nothing of the translation issues), while targeting
+ * them for their separate audiences. Ouch.
+ */
+public class PGraphics extends PImage implements PConstants {
+
+ // ........................................................
+
+ // width and height are already inherited from PImage
+
+
+ /// width minus one (useful for many calculations)
+ protected int width1;
+
+ /// height minus one (useful for many calculations)
+ protected int height1;
+
+ /// width * height (useful for many calculations)
+ public int pixelCount;
+
+ /// true if smoothing is enabled (read-only)
+ public boolean smooth = false;
+
+ // ........................................................
+
+ /// true if defaults() has been called a first time
+ protected boolean settingsInited;
+
+ /// set to a PGraphics object being used inside a beginRaw/endRaw() block
+ protected PGraphics raw;
+
+ // ........................................................
+
+ /** path to the file being saved for this renderer (if any) */
+ protected String path;
+
+ /**
+ * true if this is the main drawing surface for a particular sketch.
+ * This would be set to false for an offscreen buffer or if it were
+ * created any other way than size(). When this is set, the listeners
+ * are also added to the sketch.
+ */
+ protected boolean primarySurface;
+
+ // ........................................................
+
+ /**
+ * Array of hint[] items. These are hacks to get around various
+ * temporary workarounds inside the environment.
+ *
+ * Note that this array cannot be static, as a hint() may result in a
+ * runtime change specific to a renderer. For instance, calling
+ * hint(DISABLE_DEPTH_TEST) has to call glDisable() right away on an
+ * instance of PGraphicsOpenGL.
+ *
+ * The hints[] array is allocated early on because it might
+ * be used inside beginDraw(), allocate(), etc.
+ */
+ protected boolean[] hints = new boolean[HINT_COUNT];
+
+
+ ////////////////////////////////////////////////////////////
+
+ // STYLE PROPERTIES
+
+ // Also inherits imageMode() and smooth() (among others) from PImage.
+
+ /** The current colorMode */
+ public int colorMode; // = RGB;
+
+ /** Max value for red (or hue) set by colorMode */
+ public float colorModeX; // = 255;
+
+ /** Max value for green (or saturation) set by colorMode */
+ public float colorModeY; // = 255;
+
+ /** Max value for blue (or value) set by colorMode */
+ public float colorModeZ; // = 255;
+
+ /** Max value for alpha set by colorMode */
+ public float colorModeA; // = 255;
+
+ /** True if colors are not in the range 0..1 */
+ boolean colorModeScale; // = true;
+
+ /** True if colorMode(RGB, 255) */
+ boolean colorModeDefault; // = true;
+
+ // ........................................................
+
+ // Tint color for images
+
+ /**
+ * True if tint() is enabled (read-only).
+ *
+ * Using tint/tintColor seems a better option for naming than
+ * tintEnabled/tint because the latter seems ugly, even though
+ * g.tint as the actual color seems a little more intuitive,
+ * it's just that g.tintEnabled is even more unintuitive.
+ * Same goes for fill and stroke, et al.
+ */
+ public boolean tint;
+
+ /** tint that was last set (read-only) */
+ public int tintColor;
+
+ protected boolean tintAlpha;
+ protected float tintR, tintG, tintB, tintA;
+ protected int tintRi, tintGi, tintBi, tintAi;
+
+ // ........................................................
+
+ // Fill color
+
+ /** true if fill() is enabled, (read-only) */
+ public boolean fill;
+
+ /** fill that was last set (read-only) */
+ public int fillColor = 0xffFFFFFF;
+
+ protected boolean fillAlpha;
+ protected float fillR, fillG, fillB, fillA;
+ protected int fillRi, fillGi, fillBi, fillAi;
+
+ // ........................................................
+
+ // Stroke color
+
+ /** true if stroke() is enabled, (read-only) */
+ public boolean stroke;
+
+ /** stroke that was last set (read-only) */
+ public int strokeColor = 0xff000000;
+
+ protected boolean strokeAlpha;
+ protected float strokeR, strokeG, strokeB, strokeA;
+ protected int strokeRi, strokeGi, strokeBi, strokeAi;
+
+ // ........................................................
+
+ // Additional stroke properties
+
+ static protected final float DEFAULT_STROKE_WEIGHT = 1;
+ static protected final int DEFAULT_STROKE_JOIN = MITER;
+ static protected final int DEFAULT_STROKE_CAP = ROUND;
+
+ /**
+ * Last value set by strokeWeight() (read-only). This has a default
+ * setting, rather than fighting with renderers about whether that
+ * renderer supports thick lines.
+ */
+ public float strokeWeight = DEFAULT_STROKE_WEIGHT;
+
+ /**
+ * Set by strokeJoin() (read-only). This has a default setting
+ * so that strokeJoin() need not be called by defaults,
+ * because subclasses may not implement it (i.e. PGraphicsGL)
+ */
+ public int strokeJoin = DEFAULT_STROKE_JOIN;
+
+ /**
+ * Set by strokeCap() (read-only). This has a default setting
+ * so that strokeCap() need not be called by defaults,
+ * because subclasses may not implement it (i.e. PGraphicsGL)
+ */
+ public int strokeCap = DEFAULT_STROKE_CAP;
+
+ // ........................................................
+
+ // Shape placement properties
+
+ // imageMode() is inherited from PImage
+
+ /** The current rect mode (read-only) */
+ public int rectMode;
+
+ /** The current ellipse mode (read-only) */
+ public int ellipseMode;
+
+ /** The current shape alignment mode (read-only) */
+ public int shapeMode;
+
+ /** The current image alignment (read-only) */
+ public int imageMode = CORNER;
+
+ // ........................................................
+
+ // Text and font properties
+
+ /** The current text font (read-only) */
+ public PFont textFont;
+
+ /** The current text align (read-only) */
+ public int textAlign = LEFT;
+
+ /** The current vertical text alignment (read-only) */
+ public int textAlignY = BASELINE;
+
+ /** The current text mode (read-only) */
+ public int textMode = MODEL;
+
+ /** The current text size (read-only) */
+ public float textSize;
+
+ /** The current text leading (read-only) */
+ public float textLeading;
+
+ // ........................................................
+
+ // Material properties
+
+// PMaterial material;
+// PMaterial[] materialStack;
+// int materialStackPointer;
+
+ public float ambientR, ambientG, ambientB;
+ public float specularR, specularG, specularB;
+ public float emissiveR, emissiveG, emissiveB;
+ public float shininess;
+
+
+ // Style stack
+
+ static final int STYLE_STACK_DEPTH = 64;
+ PStyle[] styleStack = new PStyle[STYLE_STACK_DEPTH];
+ int styleStackDepth;
+
+
+ ////////////////////////////////////////////////////////////
+
+
+ /** Last background color that was set, zero if an image */
+ public int backgroundColor = 0xffCCCCCC;
+
+ protected boolean backgroundAlpha;
+ protected float backgroundR, backgroundG, backgroundB, backgroundA;
+ protected int backgroundRi, backgroundGi, backgroundBi, backgroundAi;
+
+ // ........................................................
+
+ /**
+ * Current model-view matrix transformation of the form m[row][column],
+ * which is a "column vector" (as opposed to "row vector") matrix.
+ */
+// PMatrix matrix;
+// public float m00, m01, m02, m03;
+// public float m10, m11, m12, m13;
+// public float m20, m21, m22, m23;
+// public float m30, m31, m32, m33;
+
+// static final int MATRIX_STACK_DEPTH = 32;
+// float[][] matrixStack = new float[MATRIX_STACK_DEPTH][16];
+// float[][] matrixInvStack = new float[MATRIX_STACK_DEPTH][16];
+// int matrixStackDepth;
+
+ static final int MATRIX_STACK_DEPTH = 32;
+
+ // ........................................................
+
+ /**
+ * Java AWT Image object associated with this renderer. For P2D and P3D,
+ * this will be associated with their MemoryImageSource. For PGraphicsJava2D,
+ * it will be the offscreen drawing buffer.
+ */
+ public Image image;
+
+ // ........................................................
+
+ // internal color for setting/calculating
+ protected float calcR, calcG, calcB, calcA;
+ protected int calcRi, calcGi, calcBi, calcAi;
+ protected int calcColor;
+ protected boolean calcAlpha;
+
+ /** The last RGB value converted to HSB */
+ int cacheHsbKey;
+ /** Result of the last conversion to HSB */
+ float[] cacheHsbValue = new float[3];
+
+ // ........................................................
+
+ /**
+ * Type of shape passed to beginShape(),
+ * zero if no shape is currently being drawn.
+ */
+ protected int shape;
+
+ // vertices
+ static final int DEFAULT_VERTICES = 512;
+ protected float vertices[][] =
+ new float[DEFAULT_VERTICES][VERTEX_FIELD_COUNT];
+ protected int vertexCount; // total number of vertices
+
+ // ........................................................
+
+ protected boolean bezierInited = false;
+ public int bezierDetail = 20;
+
+ // used by both curve and bezier, so just init here
+ protected PMatrix3D bezierBasisMatrix =
+ new PMatrix3D(-1, 3, -3, 1,
+ 3, -6, 3, 0,
+ -3, 3, 0, 0,
+ 1, 0, 0, 0);
+
+ //protected PMatrix3D bezierForwardMatrix;
+ protected PMatrix3D bezierDrawMatrix;
+
+ // ........................................................
+
+ protected boolean curveInited = false;
+ protected int curveDetail = 20;
+ public float curveTightness = 0;
+ // catmull-rom basis matrix, perhaps with optional s parameter
+ protected PMatrix3D curveBasisMatrix;
+ protected PMatrix3D curveDrawMatrix;
+
+ protected PMatrix3D bezierBasisInverse;
+ protected PMatrix3D curveToBezierMatrix;
+
+ // ........................................................
+
+ // spline vertices
+
+ protected float curveVertices[][];
+ protected int curveVertexCount;
+
+ // ........................................................
+
+ // precalculate sin/cos lookup tables [toxi]
+ // circle resolution is determined from the actual used radii
+ // passed to ellipse() method. this will automatically take any
+ // scale transformations into account too
+
+ // [toxi 031031]
+ // changed table's precision to 0.5 degree steps
+ // introduced new vars for more flexible code
+ static final protected float sinLUT[];
+ static final protected float cosLUT[];
+ static final protected float SINCOS_PRECISION = 0.5f;
+ static final protected int SINCOS_LENGTH = (int) (360f / SINCOS_PRECISION);
+ static {
+ sinLUT = new float[SINCOS_LENGTH];
+ cosLUT = new float[SINCOS_LENGTH];
+ for (int i = 0; i < SINCOS_LENGTH; i++) {
+ sinLUT[i] = (float) Math.sin(i * DEG_TO_RAD * SINCOS_PRECISION);
+ cosLUT[i] = (float) Math.cos(i * DEG_TO_RAD * SINCOS_PRECISION);
+ }
+ }
+
+ // ........................................................
+
+ /** The current font if a Java version of it is installed */
+ //protected Font textFontNative;
+
+ /** Metrics for the current native Java font */
+ //protected FontMetrics textFontNativeMetrics;
+
+ /** Last text position, because text often mixed on lines together */
+ protected float textX, textY, textZ;
+
+ /**
+ * Internal buffer used by the text() functions
+ * because the String object is slow
+ */
+ protected char[] textBuffer = new char[8 * 1024];
+ protected char[] textWidthBuffer = new char[8 * 1024];
+
+ protected int textBreakCount;
+ protected int[] textBreakStart;
+ protected int[] textBreakStop;
+
+ // ........................................................
+
+ public boolean edge = true;
+
+ // ........................................................
+
+ /// normal calculated per triangle
+ static protected final int NORMAL_MODE_AUTO = 0;
+ /// one normal manually specified per shape
+ static protected final int NORMAL_MODE_SHAPE = 1;
+ /// normals specified for each shape vertex
+ static protected final int NORMAL_MODE_VERTEX = 2;
+
+ /// Current mode for normals, one of AUTO, SHAPE, or VERTEX
+ protected int normalMode;
+
+ /// Keep track of how many calls to normal, to determine the mode.
+ //protected int normalCount;
+
+ /** Current normal vector. */
+ public float normalX, normalY, normalZ;
+
+ // ........................................................
+
+ /**
+ * Sets whether texture coordinates passed to
+ * vertex() calls will be based on coordinates that are
+ * based on the IMAGE or NORMALIZED.
+ */
+ public int textureMode;
+
+ /**
+ * Current horizontal coordinate for texture, will always
+ * be between 0 and 1, even if using textureMode(IMAGE).
+ */
+ public float textureU;
+
+ /** Current vertical coordinate for texture, see above. */
+ public float textureV;
+
+ /** Current image being used as a texture */
+ public PImage textureImage;
+
+ // ........................................................
+
+ // [toxi031031] new & faster sphere code w/ support flexibile resolutions
+ // will be set by sphereDetail() or 1st call to sphere()
+ float sphereX[], sphereY[], sphereZ[];
+
+ /// Number of U steps (aka "theta") around longitudinally spanning 2*pi
+ public int sphereDetailU = 0;
+ /// Number of V steps (aka "phi") along latitudinally top-to-bottom spanning pi
+ public int sphereDetailV = 0;
+
+
+ //////////////////////////////////////////////////////////////
+
+ // INTERNAL
+
+
+ /**
+ * Constructor for the PGraphics object. Use this to ensure that
+ * the defaults get set properly. In a subclass, use this(w, h)
+ * as the first line of a subclass' constructor to properly set
+ * the internal fields and defaults.
+ */
+ public PGraphics() {
+ }
+
+
+ public void setParent(PApplet parent) { // ignore
+ this.parent = parent;
+ }
+
+
+ /**
+ * Set (or unset) this as the main drawing surface. Meaning that it can
+ * safely be set to opaque (and given a default gray background), or anything
+ * else that goes along with that.
+ */
+ public void setPrimary(boolean primary) { // ignore
+ this.primarySurface = primary;
+
+ // base images must be opaque (for performance and general
+ // headache reasons.. argh, a semi-transparent opengl surface?)
+ // use createGraphics() if you want a transparent surface.
+ if (primarySurface) {
+ format = RGB;
+ }
+ }
+
+
+ public void setPath(String path) { // ignore
+ this.path = path;
+ }
+
+
+ /**
+ * The final step in setting up a renderer, set its size of this renderer.
+ * This was formerly handled by the constructor, but instead it's been broken
+ * out so that setParent/setPrimary/setPath can be handled differently.
+ *
+ * Important that this is ignored by preproc.pl because otherwise it will
+ * override setSize() in PApplet/Applet/Component, which will 1) not call
+ * super.setSize(), and 2) will cause the renderer to be resized from the
+ * event thread (EDT), causing a nasty crash as it collides with the
+ * animation thread.
+ */
+ public void setSize(int w, int h) { // ignore
+ width = w;
+ height = h;
+ width1 = width - 1;
+ height1 = height - 1;
+
+ allocate();
+ reapplySettings();
+ }
+
+
+ /**
+ * Allocate memory for this renderer. Generally will need to be implemented
+ * for all renderers.
+ */
+ protected void allocate() { }
+
+
+ /**
+ * Handle any takedown for this graphics context.
+ *
+ *
+ */
+ public void hint(int which) {
+ if (which > 0) {
+ hints[which] = true;
+ } else {
+ hints[-which] = false;
+ }
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // VERTEX SHAPES
+
+ /**
+ * Start a new shape of type POLYGON
+ */
+ public void beginShape() {
+ beginShape(POLYGON);
+ }
+
+
+ /**
+ * Start a new shape.
+ *
+ * [toxi 031031] new sphere code. removed all multiplies with
+ * radius, as scale() will take care of that anyway
+ *
+ * [toxi 031223] updated sphere code (removed modulos)
+ * and introduced sphereAt(x,y,z,r)
+ * to avoid additional translate()'s on the user/sketch side
+ *
+ * [davbol 080801] now using separate sphereDetailU/V
+ *
+ */
+ public void sphere(float r) {
+ if ((sphereDetailU < 3) || (sphereDetailV < 2)) {
+ sphereDetail(30);
+ }
+
+ pushMatrix();
+ scale(r);
+ edge(false);
+
+ // 1st ring from south pole
+ beginShape(TRIANGLE_STRIP);
+ for (int i = 0; i < sphereDetailU; i++) {
+ normal(0, -1, 0);
+ vertex(0, -1, 0);
+ normal(sphereX[i], sphereY[i], sphereZ[i]);
+ vertex(sphereX[i], sphereY[i], sphereZ[i]);
+ }
+ //normal(0, -1, 0);
+ vertex(0, -1, 0);
+ normal(sphereX[0], sphereY[0], sphereZ[0]);
+ vertex(sphereX[0], sphereY[0], sphereZ[0]);
+ endShape();
+
+ int v1,v11,v2;
+
+ // middle rings
+ int voff = 0;
+ for (int i = 2; i < sphereDetailV; i++) {
+ v1 = v11 = voff;
+ voff += sphereDetailU;
+ v2 = voff;
+ beginShape(TRIANGLE_STRIP);
+ for (int j = 0; j < sphereDetailU; j++) {
+ normal(sphereX[v1], sphereY[v1], sphereZ[v1]);
+ vertex(sphereX[v1], sphereY[v1], sphereZ[v1++]);
+ normal(sphereX[v2], sphereY[v2], sphereZ[v2]);
+ vertex(sphereX[v2], sphereY[v2], sphereZ[v2++]);
+ }
+ // close each ring
+ v1 = v11;
+ v2 = voff;
+ normal(sphereX[v1], sphereY[v1], sphereZ[v1]);
+ vertex(sphereX[v1], sphereY[v1], sphereZ[v1]);
+ normal(sphereX[v2], sphereY[v2], sphereZ[v2]);
+ vertex(sphereX[v2], sphereY[v2], sphereZ[v2]);
+ endShape();
+ }
+
+ // add the northern cap
+ beginShape(TRIANGLE_STRIP);
+ for (int i = 0; i < sphereDetailU; i++) {
+ v2 = voff + i;
+ normal(sphereX[v2], sphereY[v2], sphereZ[v2]);
+ vertex(sphereX[v2], sphereY[v2], sphereZ[v2]);
+ normal(0, 1, 0);
+ vertex(0, 1, 0);
+ }
+ normal(sphereX[voff], sphereY[voff], sphereZ[voff]);
+ vertex(sphereX[voff], sphereY[voff], sphereZ[voff]);
+ normal(0, 1, 0);
+ vertex(0, 1, 0);
+ endShape();
+
+ edge(true);
+ popMatrix();
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // BEZIER
+
+
+ /**
+ * Evalutes quadratic bezier at point t for points a, b, c, d.
+ * t varies between 0 and 1, and a and d are the on curve points,
+ * b and c are the control points. this can be done once with the
+ * x coordinates and a second time with the y coordinates to get
+ * the location of a bezier curve at t.
+ *
+ * stroke(255, 102, 0);
+ * line(85, 20, 10, 10);
+ * line(90, 90, 15, 80);
+ * stroke(0, 0, 0);
+ * bezier(85, 20, 10, 10, 90, 90, 15, 80);
+ *
+ * // draw it in gray, using 10 steps instead of the default 20
+ * // this is a slower way to do it, but useful if you need
+ * // to do things with the coordinates at each step
+ * stroke(128);
+ * beginShape(LINE_STRIP);
+ * for (int i = 0; i <= 10; i++) {
+ * float t = i / 10.0f;
+ * float x = bezierPoint(85, 10, 90, 15, t);
+ * float y = bezierPoint(20, 10, 90, 80, t);
+ * vertex(x, y);
+ * }
+ * endShape();
+ */
+ public float bezierPoint(float a, float b, float c, float d, float t) {
+ float t1 = 1.0f - t;
+ return a*t1*t1*t1 + 3*b*t*t1*t1 + 3*c*t*t*t1 + d*t*t*t;
+ }
+
+
+ /**
+ * Provide the tangent at the given point on the bezier curve.
+ * Fix from davbol for 0136.
+ */
+ public float bezierTangent(float a, float b, float c, float d, float t) {
+ return (3*t*t * (-a+3*b-3*c+d) +
+ 6*t * (a-2*b+c) +
+ 3 * (-a+b));
+ }
+
+
+ protected void bezierInitCheck() {
+ if (!bezierInited) {
+ bezierInit();
+ }
+ }
+
+
+ protected void bezierInit() {
+ // overkill to be broken out, but better parity with the curve stuff below
+ bezierDetail(bezierDetail);
+ bezierInited = true;
+ }
+
+
+ public void bezierDetail(int detail) {
+ bezierDetail = detail;
+
+ if (bezierDrawMatrix == null) {
+ bezierDrawMatrix = new PMatrix3D();
+ }
+
+ // setup matrix for forward differencing to speed up drawing
+ splineForward(detail, bezierDrawMatrix);
+
+ // multiply the basis and forward diff matrices together
+ // saves much time since this needn't be done for each curve
+ //mult_spline_matrix(bezierForwardMatrix, bezier_basis, bezierDrawMatrix, 4);
+ //bezierDrawMatrix.set(bezierForwardMatrix);
+ bezierDrawMatrix.apply(bezierBasisMatrix);
+ }
+
+
+ /**
+ * Draw a cubic bezier curve. The first and last points are
+ * the on-curve points. The middle two are the 'control' points,
+ * or 'handles' in an application like Illustrator.
+ * beginShape();
+ * vertex(x1, y1);
+ * bezierVertex(x2, y2, x3, y3, x4, y4);
+ * endShape();
+ *
+ * In Postscript-speak, this would be:
+ * moveto(x1, y1);
+ * curveto(x2, y2, x3, y3, x4, y4);
+ * If you were to try and continue that curve like so:
+ * curveto(x5, y5, x6, y6, x7, y7);
+ * This would be done in processing by adding these statements:
+ * bezierVertex(x5, y5, x6, y6, x7, y7)
+ *
+ * To draw a quadratic (instead of cubic) curve,
+ * use the control point twice by doubling it:
+ * bezier(x1, y1, cx, cy, cx, cy, x2, y2);
+ */
+ public void bezier(float x1, float y1,
+ float x2, float y2,
+ float x3, float y3,
+ float x4, float y4) {
+ beginShape();
+ vertex(x1, y1);
+ bezierVertex(x2, y2, x3, y3, x4, y4);
+ endShape();
+ }
+
+
+ public void bezier(float x1, float y1, float z1,
+ float x2, float y2, float z2,
+ float x3, float y3, float z3,
+ float x4, float y4, float z4) {
+ beginShape();
+ vertex(x1, y1, z1);
+ bezierVertex(x2, y2, z2,
+ x3, y3, z3,
+ x4, y4, z4);
+ endShape();
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // CATMULL-ROM CURVE
+
+
+ /**
+ * Get a location along a catmull-rom curve segment.
+ *
+ * @param t Value between zero and one for how far along the segment
+ */
+ public float curvePoint(float a, float b, float c, float d, float t) {
+ curveInitCheck();
+
+ float tt = t * t;
+ float ttt = t * tt;
+ PMatrix3D cb = curveBasisMatrix;
+
+ // not optimized (and probably need not be)
+ return (a * (ttt*cb.m00 + tt*cb.m10 + t*cb.m20 + cb.m30) +
+ b * (ttt*cb.m01 + tt*cb.m11 + t*cb.m21 + cb.m31) +
+ c * (ttt*cb.m02 + tt*cb.m12 + t*cb.m22 + cb.m32) +
+ d * (ttt*cb.m03 + tt*cb.m13 + t*cb.m23 + cb.m33));
+ }
+
+
+ /**
+ * Calculate the tangent at a t value (0..1) on a Catmull-Rom curve.
+ * Code thanks to Dave Bollinger (Bug #715)
+ */
+ public float curveTangent(float a, float b, float c, float d, float t) {
+ curveInitCheck();
+
+ float tt3 = t * t * 3;
+ float t2 = t * 2;
+ PMatrix3D cb = curveBasisMatrix;
+
+ // not optimized (and probably need not be)
+ return (a * (tt3*cb.m00 + t2*cb.m10 + cb.m20) +
+ b * (tt3*cb.m01 + t2*cb.m11 + cb.m21) +
+ c * (tt3*cb.m02 + t2*cb.m12 + cb.m22) +
+ d * (tt3*cb.m03 + t2*cb.m13 + cb.m23) );
+ }
+
+
+ public void curveDetail(int detail) {
+ curveDetail = detail;
+ curveInit();
+ }
+
+
+ public void curveTightness(float tightness) {
+ curveTightness = tightness;
+ curveInit();
+ }
+
+
+ protected void curveInitCheck() {
+ if (!curveInited) {
+ curveInit();
+ }
+ }
+
+
+ /**
+ * Set the number of segments to use when drawing a Catmull-Rom
+ * curve, and setting the s parameter, which defines how tightly
+ * the curve fits to each vertex. Catmull-Rom curves are actually
+ * a subset of this curve type where the s is set to zero.
+ *
+ * beginShape();
+ * curveVertex(x1, y1);
+ * curveVertex(x2, y2);
+ * curveVertex(x3, y3);
+ * curveVertex(x4, y4);
+ * endShape();
+ *
+ */
+ public void curve(float x1, float y1,
+ float x2, float y2,
+ float x3, float y3,
+ float x4, float y4) {
+ beginShape();
+ curveVertex(x1, y1);
+ curveVertex(x2, y2);
+ curveVertex(x3, y3);
+ curveVertex(x4, y4);
+ endShape();
+ }
+
+
+ public void curve(float x1, float y1, float z1,
+ float x2, float y2, float z2,
+ float x3, float y3, float z3,
+ float x4, float y4, float z4) {
+ beginShape();
+ curveVertex(x1, y1, z1);
+ curveVertex(x2, y2, z2);
+ curveVertex(x3, y3, z3);
+ curveVertex(x4, y4, z4);
+ endShape();
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // SPLINE UTILITY FUNCTIONS (used by both Bezier and Catmull-Rom)
+
+
+ /**
+ * Setup forward-differencing matrix to be used for speedy
+ * curve rendering. It's based on using a specific number
+ * of curve segments and just doing incremental adds for each
+ * vertex of the segment, rather than running the mathematically
+ * expensive cubic equation.
+ * @param segments number of curve segments to use when drawing
+ * @param matrix target object for the new matrix
+ */
+ protected void splineForward(int segments, PMatrix3D matrix) {
+ float f = 1.0f / segments;
+ float ff = f * f;
+ float fff = ff * f;
+
+ matrix.set(0, 0, 0, 1,
+ fff, ff, f, 0,
+ 6*fff, 2*ff, 0, 0,
+ 6*fff, 0, 0, 0);
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // SMOOTHING
+
+
+ /**
+ * If true in PImage, use bilinear interpolation for copy()
+ * operations. When inherited by PGraphics, also controls shapes.
+ */
+ public void smooth() {
+ smooth = true;
+ }
+
+
+ /**
+ * Disable smoothing. See smooth().
+ */
+ public void noSmooth() {
+ smooth = false;
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // IMAGE
+
+
+ /**
+ * The mode can only be set to CORNERS, CORNER, and CENTER.
+ *
+ * Support for CENTER was added in release 0146.
+ */
+ public void imageMode(int mode) {
+ if ((mode == CORNER) || (mode == CORNERS) || (mode == CENTER)) {
+ imageMode = mode;
+ } else {
+ String msg =
+ "imageMode() only works with CORNER, CORNERS, or CENTER";
+ throw new RuntimeException(msg);
+ }
+ }
+
+
+ public void image(PImage image, float x, float y) {
+ // Starting in release 0144, image errors are simply ignored.
+ // loadImageAsync() sets width and height to -1 when loading fails.
+ if (image.width == -1 || image.height == -1) return;
+
+ if (imageMode == CORNER || imageMode == CORNERS) {
+ imageImpl(image,
+ x, y, x+image.width, y+image.height,
+ 0, 0, image.width, image.height);
+
+ } else if (imageMode == CENTER) {
+ float x1 = x - image.width/2;
+ float y1 = y - image.height/2;
+ imageImpl(image,
+ x1, y1, x1+image.width, y1+image.height,
+ 0, 0, image.width, image.height);
+ }
+ }
+
+
+ public void image(PImage image, float x, float y, float c, float d) {
+ image(image, x, y, c, d, 0, 0, image.width, image.height);
+ }
+
+
+ /**
+ * Draw an image(), also specifying u/v coordinates.
+ * In this method, the u, v coordinates are always based on image space
+ * location, regardless of the current textureMode().
+ */
+ public void image(PImage image,
+ float a, float b, float c, float d,
+ int u1, int v1, int u2, int v2) {
+ // Starting in release 0144, image errors are simply ignored.
+ // loadImageAsync() sets width and height to -1 when loading fails.
+ if (image.width == -1 || image.height == -1) return;
+
+ if (imageMode == CORNER) {
+ if (c < 0) { // reset a negative width
+ a += c; c = -c;
+ }
+ if (d < 0) { // reset a negative height
+ b += d; d = -d;
+ }
+
+ imageImpl(image,
+ a, b, a + c, b + d,
+ u1, v1, u2, v2);
+
+ } else if (imageMode == CORNERS) {
+ if (c < a) { // reverse because x2 < x1
+ float temp = a; a = c; c = temp;
+ }
+ if (d < b) { // reverse because y2 < y1
+ float temp = b; b = d; d = temp;
+ }
+
+ imageImpl(image,
+ a, b, c, d,
+ u1, v1, u2, v2);
+
+ } else if (imageMode == CENTER) {
+ // c and d are width/height
+ if (c < 0) c = -c;
+ if (d < 0) d = -d;
+ float x1 = a - c/2;
+ float y1 = b - d/2;
+
+ imageImpl(image,
+ x1, y1, x1 + c, y1 + d,
+ u1, v1, u2, v2);
+ }
+ }
+
+
+ /**
+ * Expects x1, y1, x2, y2 coordinates where (x2 >= x1) and (y2 >= y1).
+ * If tint() has been called, the image will be colored.
+ *
+ * The default implementation draws an image as a textured quad.
+ * The (u, v) coordinates are in image space (they're ints, after all..)
+ */
+ protected void imageImpl(PImage image,
+ float x1, float y1, float x2, float y2,
+ int u1, int v1, int u2, int v2) {
+ boolean savedStroke = stroke;
+// boolean savedFill = fill;
+ int savedTextureMode = textureMode;
+
+ stroke = false;
+// fill = true;
+ textureMode = IMAGE;
+
+// float savedFillR = fillR;
+// float savedFillG = fillG;
+// float savedFillB = fillB;
+// float savedFillA = fillA;
+//
+// if (tint) {
+// fillR = tintR;
+// fillG = tintG;
+// fillB = tintB;
+// fillA = tintA;
+//
+// } else {
+// fillR = 1;
+// fillG = 1;
+// fillB = 1;
+// fillA = 1;
+// }
+
+ beginShape(QUADS);
+ texture(image);
+ vertex(x1, y1, u1, v1);
+ vertex(x1, y2, u1, v2);
+ vertex(x2, y2, u2, v2);
+ vertex(x2, y1, u2, v1);
+ endShape();
+
+ stroke = savedStroke;
+// fill = savedFill;
+ textureMode = savedTextureMode;
+
+// fillR = savedFillR;
+// fillG = savedFillG;
+// fillB = savedFillB;
+// fillA = savedFillA;
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // SHAPE
+
+
+ /**
+ * Set the orientation for the shape() command (like imageMode() or rectMode()).
+ * @param mode Either CORNER, CORNERS, or CENTER.
+ */
+ public void shapeMode(int mode) {
+ this.shapeMode = mode;
+ }
+
+
+ public void shape(PShape shape) {
+ if (shape.isVisible()) { // don't do expensive matrix ops if invisible
+ if (shapeMode == CENTER) {
+ pushMatrix();
+ translate(-shape.getWidth()/2, -shape.getHeight()/2);
+ }
+
+ shape.draw(this); // needs to handle recorder too
+
+ if (shapeMode == CENTER) {
+ popMatrix();
+ }
+ }
+ }
+
+
+ /**
+ * Convenience method to draw at a particular location.
+ */
+ public void shape(PShape shape, float x, float y) {
+ if (shape.isVisible()) { // don't do expensive matrix ops if invisible
+ pushMatrix();
+
+ if (shapeMode == CENTER) {
+ translate(x - shape.getWidth()/2, y - shape.getHeight()/2);
+
+ } else if ((shapeMode == CORNER) || (shapeMode == CORNERS)) {
+ translate(x, y);
+ }
+ shape.draw(this);
+
+ popMatrix();
+ }
+ }
+
+
+ public void shape(PShape shape, float x, float y, float c, float d) {
+ if (shape.isVisible()) { // don't do expensive matrix ops if invisible
+ pushMatrix();
+
+ if (shapeMode == CENTER) {
+ // x and y are center, c and d refer to a diameter
+ translate(x - c/2f, y - d/2f);
+ scale(c / shape.getWidth(), d / shape.getHeight());
+
+ } else if (shapeMode == CORNER) {
+ translate(x, y);
+ scale(c / shape.getWidth(), d / shape.getHeight());
+
+ } else if (shapeMode == CORNERS) {
+ // c and d are x2/y2, make them into width/height
+ c -= x;
+ d -= y;
+ // then same as above
+ translate(x, y);
+ scale(c / shape.getWidth(), d / shape.getHeight());
+ }
+ shape.draw(this);
+
+ popMatrix();
+ }
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // TEXT/FONTS
+
+
+ /**
+ * Sets the alignment of the text to one of LEFT, CENTER, or RIGHT.
+ * This will also reset the vertical text alignment to BASELINE.
+ */
+ public void textAlign(int align) {
+ textAlign(align, BASELINE);
+ }
+
+
+ /**
+ * Sets the horizontal and vertical alignment of the text. The horizontal
+ * alignment can be one of LEFT, CENTER, or RIGHT. The vertical alignment
+ * can be TOP, BOTTOM, CENTER, or the BASELINE (the default).
+ */
+ public void textAlign(int alignX, int alignY) {
+ textAlign = alignX;
+ textAlignY = alignY;
+ }
+
+
+ /**
+ * Returns the ascent of the current font at the current size.
+ * This is a method, rather than a variable inside the PGraphics object
+ * because it requires calculation.
+ */
+ public float textAscent() {
+ if (textFont == null) {
+ showTextFontException("textAscent");
+ }
+ return textFont.ascent() * ((textMode == SCREEN) ? textFont.size : textSize);
+ }
+
+
+ /**
+ * Returns the descent of the current font at the current size.
+ * This is a method, rather than a variable inside the PGraphics object
+ * because it requires calculation.
+ */
+ public float textDescent() {
+ if (textFont == null) {
+ showTextFontException("textDescent");
+ }
+ return textFont.descent() * ((textMode == SCREEN) ? textFont.size : textSize);
+ }
+
+
+ /**
+ * Sets the current font. The font's size will be the "natural"
+ * size of this font (the size that was set when using "Create Font").
+ * The leading will also be reset.
+ */
+ public void textFont(PFont which) {
+ if (which != null) {
+ textFont = which;
+ if (hints[ENABLE_NATIVE_FONTS]) {
+ //if (which.font == null) {
+ which.findFont();
+ //}
+ }
+ /*
+ textFontNative = which.font;
+
+ //textFontNativeMetrics = null;
+ // changed for rev 0104 for textMode(SHAPE) in opengl
+ if (textFontNative != null) {
+ // TODO need a better way to handle this. could use reflection to get
+ // rid of the warning, but that'd be a little silly. supporting this is
+ // an artifact of supporting java 1.1, otherwise we'd use getLineMetrics,
+ // as recommended by the @deprecated flag.
+ textFontNativeMetrics =
+ Toolkit.getDefaultToolkit().getFontMetrics(textFontNative);
+ // The following is what needs to be done, however we need to be able
+ // to get the actual graphics context where the drawing is happening.
+ // For instance, parent.getGraphics() doesn't work for OpenGL since
+ // an OpenGL drawing surface is an embedded component.
+// if (parent != null) {
+// textFontNativeMetrics = parent.getGraphics().getFontMetrics(textFontNative);
+// }
+
+ // float w = font.getStringBounds(text, g2.getFontRenderContext()).getWidth();
+ }
+ */
+ textSize(which.size);
+
+ } else {
+ throw new RuntimeException(ERROR_TEXTFONT_NULL_PFONT);
+ }
+ }
+
+
+ /**
+ * Useful function to set the font and size at the same time.
+ */
+ public void textFont(PFont which, float size) {
+ textFont(which);
+ textSize(size);
+ }
+
+
+ /**
+ * Set the text leading to a specific value. If using a custom
+ * value for the text leading, you'll have to call textLeading()
+ * again after any calls to textSize().
+ */
+ public void textLeading(float leading) {
+ textLeading = leading;
+ }
+
+
+ /**
+ * Sets the text rendering/placement to be either SCREEN (direct
+ * to the screen, exact coordinates, only use the font's original size)
+ * or MODEL (the default, where text is manipulated by translate() and
+ * can have a textSize). The text size cannot be set when using
+ * textMode(SCREEN), because it uses the pixels directly from the font.
+ */
+ public void textMode(int mode) {
+ // CENTER and MODEL overlap (they're both 3)
+ if ((mode == LEFT) || (mode == RIGHT)) {
+ showWarning("Since Processing beta, textMode() is now textAlign().");
+ return;
+ }
+// if ((mode != SCREEN) && (mode != MODEL)) {
+// showError("Only textMode(SCREEN) and textMode(MODEL) " +
+// "are available with this renderer.");
+// }
+
+ if (textModeCheck(mode)) {
+ textMode = mode;
+ } else {
+ String modeStr = String.valueOf(mode);
+ switch (mode) {
+ case SCREEN: modeStr = "SCREEN"; break;
+ case MODEL: modeStr = "MODEL"; break;
+ case SHAPE: modeStr = "SHAPE"; break;
+ }
+ showWarning("textMode(" + modeStr + ") is not supported by this renderer.");
+ }
+
+ // reset the font to its natural size
+ // (helps with width calculations and all that)
+ //if (textMode == SCREEN) {
+ //textSize(textFont.size);
+ //}
+
+ //} else {
+ //throw new RuntimeException("use textFont() before textMode()");
+ //}
+ }
+
+
+ protected boolean textModeCheck(int mode) {
+ return true;
+ }
+
+
+ /**
+ * Sets the text size, also resets the value for the leading.
+ */
+ public void textSize(float size) {
+ if (textFont != null) {
+// if ((textMode == SCREEN) && (size != textFont.size)) {
+// throw new RuntimeException("textSize() is ignored with " +
+// "textMode(SCREEN)");
+// }
+ textSize = size;
+ textLeading = (textAscent() + textDescent()) * 1.275f;
+
+ } else {
+ showTextFontException("textSize");
+ }
+ }
+
+
+ // ........................................................
+
+
+ public float textWidth(char c) {
+ textWidthBuffer[0] = c;
+ return textWidthImpl(textWidthBuffer, 0, 1);
+ }
+
+
+ /**
+ * Return the width of a line of text. If the text has multiple
+ * lines, this returns the length of the longest line.
+ */
+ public float textWidth(String str) {
+ if (textFont == null) {
+ showTextFontException("textWidth");
+ }
+
+ int length = str.length();
+ if (length > textWidthBuffer.length) {
+ textWidthBuffer = new char[length + 10];
+ }
+ str.getChars(0, length, textWidthBuffer, 0);
+
+ float wide = 0;
+ int index = 0;
+ int start = 0;
+
+ while (index < length) {
+ if (textWidthBuffer[index] == '\n') {
+ wide = Math.max(wide, textWidthImpl(textWidthBuffer, start, index));
+ start = index+1;
+ }
+ index++;
+ }
+ if (start < length) {
+ wide = Math.max(wide, textWidthImpl(textWidthBuffer, start, index));
+ }
+ return wide;
+ }
+
+
+ /**
+ * Implementation of returning the text width of
+ * the chars [start, stop) in the buffer.
+ * Unlike the previous version that was inside PFont, this will
+ * return the size not of a 1 pixel font, but the actual current size.
+ */
+ protected float textWidthImpl(char buffer[], int start, int stop) {
+ float wide = 0;
+ for (int i = start; i < stop; i++) {
+ // could add kerning here, but it just ain't implemented
+ wide += textFont.width(buffer[i]) * textSize;
+ }
+ return wide;
+ }
+
+
+ // ........................................................
+
+
+ /**
+ * Write text where we just left off.
+ */
+ public void text(char c) {
+ text(c, textX, textY, textZ);
+ }
+
+
+ /**
+ * Draw a single character on screen.
+ * Extremely slow when used with textMode(SCREEN) and Java 2D,
+ * because loadPixels has to be called first and updatePixels last.
+ */
+ public void text(char c, float x, float y) {
+ if (textFont == null) {
+ showTextFontException("text");
+ }
+
+ if (textMode == SCREEN) loadPixels();
+
+ if (textAlignY == CENTER) {
+ y += textAscent() / 2;
+ } else if (textAlignY == TOP) {
+ y += textAscent();
+ } else if (textAlignY == BOTTOM) {
+ y -= textDescent();
+ //} else if (textAlignY == BASELINE) {
+ // do nothing
+ }
+
+ textBuffer[0] = c;
+ textLineAlignImpl(textBuffer, 0, 1, x, y);
+
+ if (textMode == SCREEN) updatePixels();
+ }
+
+
+ /**
+ * Draw a single character on screen (with a z coordinate)
+ */
+ public void text(char c, float x, float y, float z) {
+// if ((z != 0) && (textMode == SCREEN)) {
+// String msg = "textMode(SCREEN) cannot have a z coordinate";
+// throw new RuntimeException(msg);
+// }
+
+ if (z != 0) translate(0, 0, z); // slowness, badness
+
+ text(c, x, y);
+ textZ = z;
+
+ if (z != 0) translate(0, 0, -z);
+ }
+
+
+ /**
+ * Write text where we just left off.
+ */
+ public void text(String str) {
+ text(str, textX, textY, textZ);
+ }
+
+
+ /**
+ * Draw a chunk of text.
+ * Newlines that are \n (Unix newline or linefeed char, ascii 10)
+ * are honored, but \r (carriage return, Windows and Mac OS) are
+ * ignored.
+ */
+ public void text(String str, float x, float y) {
+ if (textFont == null) {
+ showTextFontException("text");
+ }
+
+ if (textMode == SCREEN) loadPixels();
+
+ int length = str.length();
+ if (length > textBuffer.length) {
+ textBuffer = new char[length + 10];
+ }
+ str.getChars(0, length, textBuffer, 0);
+ text(textBuffer, 0, length, x, y);
+ }
+
+
+ /**
+ * Method to draw text from an array of chars. This method will usually be
+ * more efficient than drawing from a String object, because the String will
+ * not be converted to a char array before drawing.
+ */
+ public void text(char[] chars, int start, int stop, float x, float y) {
+ // If multiple lines, sum the height of the additional lines
+ float high = 0; //-textAscent();
+ for (int i = start; i < stop; i++) {
+ if (chars[i] == '\n') {
+ high += textLeading;
+ }
+ }
+ if (textAlignY == CENTER) {
+ // for a single line, this adds half the textAscent to y
+ // for multiple lines, subtract half the additional height
+ //y += (textAscent() - textDescent() - high)/2;
+ y += (textAscent() - high)/2;
+ } else if (textAlignY == TOP) {
+ // for a single line, need to add textAscent to y
+ // for multiple lines, no different
+ y += textAscent();
+ } else if (textAlignY == BOTTOM) {
+ // for a single line, this is just offset by the descent
+ // for multiple lines, subtract leading for each line
+ y -= textDescent() + high;
+ //} else if (textAlignY == BASELINE) {
+ // do nothing
+ }
+
+// int start = 0;
+ int index = 0;
+ while (index < stop) { //length) {
+ if (chars[index] == '\n') {
+ textLineAlignImpl(chars, start, index, x, y);
+ start = index + 1;
+ y += textLeading;
+ }
+ index++;
+ }
+ if (start < stop) { //length) {
+ textLineAlignImpl(chars, start, index, x, y);
+ }
+ if (textMode == SCREEN) updatePixels();
+ }
+
+
+ /**
+ * Same as above but with a z coordinate.
+ */
+ public void text(String str, float x, float y, float z) {
+ if (z != 0) translate(0, 0, z); // slow!
+
+ text(str, x, y);
+ textZ = z;
+
+ if (z != 0) translate(0, 0, -z); // inaccurate!
+ }
+
+
+ public void text(char[] chars, int start, int stop,
+ float x, float y, float z) {
+ if (z != 0) translate(0, 0, z); // slow!
+
+ text(chars, start, stop, x, y);
+ textZ = z;
+
+ if (z != 0) translate(0, 0, -z); // inaccurate!
+ }
+
+
+ /**
+ * Draw text in a box that is constrained to a particular size.
+ * The current rectMode() determines what the coordinates mean
+ * (whether x1/y1/x2/y2 or x/y/w/h).
+ *
+ * Note that the x,y coords of the start of the box
+ * will align with the *ascent* of the text, not the baseline,
+ * as is the case for the other text() functions.
+ *
+ * Newlines that are \n (Unix newline or linefeed char, ascii 10)
+ * are honored, and \r (carriage return, Windows and Mac OS) are
+ * ignored.
+ */
+ public void text(String str, float x1, float y1, float x2, float y2) {
+ if (textFont == null) {
+ showTextFontException("text");
+ }
+
+ if (textMode == SCREEN) loadPixels();
+
+ float hradius, vradius;
+ switch (rectMode) {
+ case CORNER:
+ x2 += x1; y2 += y1;
+ break;
+ case RADIUS:
+ hradius = x2;
+ vradius = y2;
+ x2 = x1 + hradius;
+ y2 = y1 + vradius;
+ x1 -= hradius;
+ y1 -= vradius;
+ break;
+ case CENTER:
+ hradius = x2 / 2.0f;
+ vradius = y2 / 2.0f;
+ x2 = x1 + hradius;
+ y2 = y1 + vradius;
+ x1 -= hradius;
+ y1 -= vradius;
+ }
+ if (x2 < x1) {
+ float temp = x1; x1 = x2; x2 = temp;
+ }
+ if (y2 < y1) {
+ float temp = y1; y1 = y2; y2 = temp;
+ }
+
+// float currentY = y1;
+ float boxWidth = x2 - x1;
+
+// // ala illustrator, the text itself must fit inside the box
+// currentY += textAscent(); //ascent() * textSize;
+// // if the box is already too small, tell em to f off
+// if (currentY > y2) return;
+
+ float spaceWidth = textWidth(' ');
+
+ if (textBreakStart == null) {
+ textBreakStart = new int[20];
+ textBreakStop = new int[20];
+ }
+ textBreakCount = 0;
+
+ int length = str.length();
+ if (length + 1 > textBuffer.length) {
+ textBuffer = new char[length + 1];
+ }
+ str.getChars(0, length, textBuffer, 0);
+ // add a fake newline to simplify calculations
+ textBuffer[length++] = '\n';
+
+ int sentenceStart = 0;
+ for (int i = 0; i < length; i++) {
+ if (textBuffer[i] == '\n') {
+// currentY = textSentence(textBuffer, sentenceStart, i,
+// lineX, boxWidth, currentY, y2, spaceWidth);
+ boolean legit =
+ textSentence(textBuffer, sentenceStart, i, boxWidth, spaceWidth);
+ if (!legit) break;
+// if (Float.isNaN(currentY)) break; // word too big (or error)
+// if (currentY > y2) break; // past the box
+ sentenceStart = i + 1;
+ }
+ }
+
+ // lineX is the position where the text starts, which is adjusted
+ // to left/center/right based on the current textAlign
+ float lineX = x1; //boxX1;
+ if (textAlign == CENTER) {
+ lineX = lineX + boxWidth/2f;
+ } else if (textAlign == RIGHT) {
+ lineX = x2; //boxX2;
+ }
+
+ float boxHeight = y2 - y1;
+ //int lineFitCount = 1 + PApplet.floor((boxHeight - textAscent()) / textLeading);
+ // incorporate textAscent() for the top (baseline will be y1 + ascent)
+ // and textDescent() for the bottom, so that lower parts of letters aren't
+ // outside the box. [0151]
+ float topAndBottom = textAscent() + textDescent();
+ int lineFitCount = 1 + PApplet.floor((boxHeight - topAndBottom) / textLeading);
+ int lineCount = Math.min(textBreakCount, lineFitCount);
+
+ if (textAlignY == CENTER) {
+ float lineHigh = textAscent() + textLeading * (lineCount - 1);
+ float y = y1 + textAscent() + (boxHeight - lineHigh) / 2;
+ for (int i = 0; i < lineCount; i++) {
+ textLineAlignImpl(textBuffer, textBreakStart[i], textBreakStop[i], lineX, y);
+ y += textLeading;
+ }
+
+ } else if (textAlignY == BOTTOM) {
+ float y = y2 - textDescent() - textLeading * (lineCount - 1);
+ for (int i = 0; i < lineCount; i++) {
+ textLineAlignImpl(textBuffer, textBreakStart[i], textBreakStop[i], lineX, y);
+ y += textLeading;
+ }
+
+ } else { // TOP or BASELINE just go to the default
+ float y = y1 + textAscent();
+ for (int i = 0; i < lineCount; i++) {
+ textLineAlignImpl(textBuffer, textBreakStart[i], textBreakStop[i], lineX, y);
+ y += textLeading;
+ }
+ }
+
+ if (textMode == SCREEN) updatePixels();
+ }
+
+
+ /**
+ * Emit a sentence of text, defined as a chunk of text without any newlines.
+ * @param stop non-inclusive, the end of the text in question
+ */
+ protected boolean textSentence(char[] buffer, int start, int stop,
+ float boxWidth, float spaceWidth) {
+ float runningX = 0;
+
+ // Keep track of this separately from index, since we'll need to back up
+ // from index when breaking words that are too long to fit.
+ int lineStart = start;
+ int wordStart = start;
+ int index = start;
+ while (index <= stop) {
+ // boundary of a word or end of this sentence
+ if ((buffer[index] == ' ') || (index == stop)) {
+ float wordWidth = textWidthImpl(buffer, wordStart, index);
+
+ if (runningX + wordWidth > boxWidth) {
+ if (runningX != 0) {
+ // Next word is too big, output the current line and advance
+ index = wordStart;
+ textSentenceBreak(lineStart, index);
+ // Eat whitespace because multiple spaces don't count for s*
+ // when they're at the end of a line.
+ while ((index < stop) && (buffer[index] == ' ')) {
+ index++;
+ }
+ } else { // (runningX == 0)
+ // If this is the first word on the line, and its width is greater
+ // than the width of the text box, then break the word where at the
+ // max width, and send the rest of the word to the next line.
+ do {
+ index--;
+ if (index == wordStart) {
+ // Not a single char will fit on this line. screw 'em.
+ //System.out.println("screw you");
+ return false; //Float.NaN;
+ }
+ wordWidth = textWidthImpl(buffer, wordStart, index);
+ } while (wordWidth > boxWidth);
+
+ //textLineImpl(buffer, lineStart, index, x, y);
+ textSentenceBreak(lineStart, index);
+ }
+ lineStart = index;
+ wordStart = index;
+ runningX = 0;
+
+ } else if (index == stop) {
+ // last line in the block, time to unload
+ //textLineImpl(buffer, lineStart, index, x, y);
+ textSentenceBreak(lineStart, index);
+// y += textLeading;
+ index++;
+
+ } else { // this word will fit, just add it to the line
+ runningX += wordWidth + spaceWidth;
+ wordStart = index + 1; // move on to the next word
+ index++;
+ }
+ } else { // not a space or the last character
+ index++; // this is just another letter
+ }
+ }
+// return y;
+ return true;
+ }
+
+
+ protected void textSentenceBreak(int start, int stop) {
+ if (textBreakCount == textBreakStart.length) {
+ textBreakStart = PApplet.expand(textBreakStart);
+ textBreakStop = PApplet.expand(textBreakStop);
+ }
+ textBreakStart[textBreakCount] = start;
+ textBreakStop[textBreakCount] = stop;
+ textBreakCount++;
+ }
+
+
+ public void text(String s, float x1, float y1, float x2, float y2, float z) {
+ if (z != 0) translate(0, 0, z); // slowness, badness
+
+ text(s, x1, y1, x2, y2);
+ textZ = z;
+
+ if (z != 0) translate(0, 0, -z); // TEMPORARY HACK! SLOW!
+ }
+
+
+ public void text(int num, float x, float y) {
+ text(String.valueOf(num), x, y);
+ }
+
+
+ public void text(int num, float x, float y, float z) {
+ text(String.valueOf(num), x, y, z);
+ }
+
+
+ /**
+ * This does a basic number formatting, to avoid the
+ * generally ugly appearance of printing floats.
+ * Users who want more control should use their own nf() cmmand,
+ * or if they want the long, ugly version of float,
+ * use String.valueOf() to convert the float to a String first.
+ */
+ public void text(float num, float x, float y) {
+ text(PApplet.nfs(num, 0, 3), x, y);
+ }
+
+
+ public void text(float num, float x, float y, float z) {
+ text(PApplet.nfs(num, 0, 3), x, y, z);
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // TEXT IMPL
+
+ // These are most likely to be overridden by subclasses, since the other
+ // (public) functions handle generic features like setting alignment.
+
+
+ /**
+ * Handles placement of a text line, then calls textLineImpl
+ * to actually render at the specific point.
+ */
+ protected void textLineAlignImpl(char buffer[], int start, int stop,
+ float x, float y) {
+ if (textAlign == CENTER) {
+ x -= textWidthImpl(buffer, start, stop) / 2f;
+
+ } else if (textAlign == RIGHT) {
+ x -= textWidthImpl(buffer, start, stop);
+ }
+
+ textLineImpl(buffer, start, stop, x, y);
+ }
+
+
+ /**
+ * Implementation of actual drawing for a line of text.
+ */
+ protected void textLineImpl(char buffer[], int start, int stop,
+ float x, float y) {
+ for (int index = start; index < stop; index++) {
+ textCharImpl(buffer[index], x, y);
+
+ // this doesn't account for kerning
+ x += textWidth(buffer[index]);
+ }
+ textX = x;
+ textY = y;
+ textZ = 0; // this will get set by the caller if non-zero
+ }
+
+
+ protected void textCharImpl(char ch, float x, float y) { //, float z) {
+ int index = textFont.index(ch);
+ if (index == -1) return;
+
+ PImage glyph = textFont.images[index];
+
+ if (textMode == MODEL) {
+ float high = (float) textFont.height[index] / textFont.fheight;
+ float bwidth = (float) textFont.width[index] / textFont.fwidth;
+ float lextent = (float) textFont.leftExtent[index] / textFont.fwidth;
+ float textent = (float) textFont.topExtent[index] / textFont.fheight;
+
+ float x1 = x + lextent * textSize;
+ float y1 = y - textent * textSize;
+ float x2 = x1 + bwidth * textSize;
+ float y2 = y1 + high * textSize;
+
+ textCharModelImpl(glyph,
+ x1, y1, x2, y2,
+ //x1, y1, z, x2, y2, z,
+ textFont.width[index], textFont.height[index]);
+
+ } else if (textMode == SCREEN) {
+ int xx = (int) x + textFont.leftExtent[index];;
+ int yy = (int) y - textFont.topExtent[index];
+
+ int w0 = textFont.width[index];
+ int h0 = textFont.height[index];
+
+ textCharScreenImpl(glyph, xx, yy, w0, h0);
+ }
+ }
+
+
+ protected void textCharModelImpl(PImage glyph,
+ float x1, float y1, //float z1,
+ float x2, float y2, //float z2,
+ int u2, int v2) {
+ boolean savedTint = tint;
+ int savedTintColor = tintColor;
+ float savedTintR = tintR;
+ float savedTintG = tintG;
+ float savedTintB = tintB;
+ float savedTintA = tintA;
+ boolean savedTintAlpha = tintAlpha;
+
+ tint = true;
+ tintColor = fillColor;
+ tintR = fillR;
+ tintG = fillG;
+ tintB = fillB;
+ tintA = fillA;
+ tintAlpha = fillAlpha;
+
+ imageImpl(glyph, x1, y1, x2, y2, 0, 0, u2, v2);
+
+ tint = savedTint;
+ tintColor = savedTintColor;
+ tintR = savedTintR;
+ tintG = savedTintG;
+ tintB = savedTintB;
+ tintA = savedTintA;
+ tintAlpha = savedTintAlpha;
+ }
+
+
+ protected void textCharScreenImpl(PImage glyph,
+ int xx, int yy,
+ int w0, int h0) {
+ int x0 = 0;
+ int y0 = 0;
+
+ if ((xx >= width) || (yy >= height) ||
+ (xx + w0 < 0) || (yy + h0 < 0)) return;
+
+ if (xx < 0) {
+ x0 -= xx;
+ w0 += xx;
+ xx = 0;
+ }
+ if (yy < 0) {
+ y0 -= yy;
+ h0 += yy;
+ yy = 0;
+ }
+ if (xx + w0 > width) {
+ w0 -= ((xx + w0) - width);
+ }
+ if (yy + h0 > height) {
+ h0 -= ((yy + h0) - height);
+ }
+
+ int fr = fillRi;
+ int fg = fillGi;
+ int fb = fillBi;
+ int fa = fillAi;
+
+ int pixels1[] = glyph.pixels; //images[glyph].pixels;
+
+ // TODO this can be optimized a bit
+ for (int row = y0; row < y0 + h0; row++) {
+ for (int col = x0; col < x0 + w0; col++) {
+ int a1 = (fa * pixels1[row * textFont.twidth + col]) >> 8;
+ int a2 = a1 ^ 0xff;
+ //int p1 = pixels1[row * glyph.width + col];
+ int p2 = pixels[(yy + row-y0)*width + (xx+col-x0)];
+
+ pixels[(yy + row-y0)*width + xx+col-x0] =
+ (0xff000000 |
+ (((a1 * fr + a2 * ((p2 >> 16) & 0xff)) & 0xff00) << 8) |
+ (( a1 * fg + a2 * ((p2 >> 8) & 0xff)) & 0xff00) |
+ (( a1 * fb + a2 * ( p2 & 0xff)) >> 8));
+ }
+ }
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // MATRIX STACK
+
+
+ /**
+ * Push a copy of the current transformation matrix onto the stack.
+ */
+ public void pushMatrix() {
+ showMethodWarning("pushMatrix");
+ }
+
+
+ /**
+ * Replace the current transformation matrix with the top of the stack.
+ */
+ public void popMatrix() {
+ showMethodWarning("popMatrix");
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // MATRIX TRANSFORMATIONS
+
+
+ /**
+ * Translate in X and Y.
+ */
+ public void translate(float tx, float ty) {
+ showMissingWarning("translate");
+ }
+
+
+ /**
+ * Translate in X, Y, and Z.
+ */
+ public void translate(float tx, float ty, float tz) {
+ showMissingWarning("translate");
+ }
+
+
+ /**
+ * Two dimensional rotation.
+ *
+ * Same as rotateZ (this is identical to a 3D rotation along the z-axis)
+ * but included for clarity. It'd be weird for people drawing 2D graphics
+ * to be using rotateZ. And they might kick our a-- for the confusion.
+ *
+ * Additional background.
+ */
+ public void rotate(float angle) {
+ showMissingWarning("rotate");
+ }
+
+
+ /**
+ * Rotate around the X axis.
+ */
+ public void rotateX(float angle) {
+ showMethodWarning("rotateX");
+ }
+
+
+ /**
+ * Rotate around the Y axis.
+ */
+ public void rotateY(float angle) {
+ showMethodWarning("rotateY");
+ }
+
+
+ /**
+ * Rotate around the Z axis.
+ *
+ * The functions rotate() and rotateZ() are identical, it's just that it make
+ * sense to have rotate() and then rotateX() and rotateY() when using 3D;
+ * nor does it make sense to use a function called rotateZ() if you're only
+ * doing things in 2D. so we just decided to have them both be the same.
+ */
+ public void rotateZ(float angle) {
+ showMethodWarning("rotateZ");
+ }
+
+
+ /**
+ * Rotate about a vector in space. Same as the glRotatef() function.
+ */
+ public void rotate(float angle, float vx, float vy, float vz) {
+ showMissingWarning("rotate");
+ }
+
+
+ /**
+ * Scale in all dimensions.
+ */
+ public void scale(float s) {
+ showMissingWarning("scale");
+ }
+
+
+ /**
+ * Scale in X and Y. Equivalent to scale(sx, sy, 1).
+ *
+ * Not recommended for use in 3D, because the z-dimension is just
+ * scaled by 1, since there's no way to know what else to scale it by.
+ */
+ public void scale(float sx, float sy) {
+ showMissingWarning("scale");
+ }
+
+
+ /**
+ * Scale in X, Y, and Z.
+ */
+ public void scale(float x, float y, float z) {
+ showMissingWarning("scale");
+ }
+
+
+ //////////////////////////////////////////////////////////////
+
+ // MATRIX FULL MONTY
+
+
+ /**
+ * Set the current transformation matrix to identity.
+ */
+ public void resetMatrix() {
+ showMethodWarning("resetMatrix");
+ }
+
+
+ public void applyMatrix(PMatrix source) {
+ if (source instanceof PMatrix2D) {
+ applyMatrix((PMatrix2D) source);
+ } else if (source instanceof PMatrix3D) {
+ applyMatrix((PMatrix3D) source);
+ }
+ }
+
+
+ public void applyMatrix(PMatrix2D source) {
+ applyMatrix(source.m00, source.m01, source.m02,
+ source.m10, source.m11, source.m12);
+ }
+
+
+ /**
+ * Apply a 3x2 affine transformation matrix.
+ */
+ public void applyMatrix(float n00, float n01, float n02,
+ float n10, float n11, float n12) {
+ showMissingWarning("applyMatrix");
+ }
+
+
+ public void applyMatrix(PMatrix3D source) {
+ applyMatrix(source.m00, source.m01, source.m02, source.m03,
+ source.m10, source.m11, source.m12, source.m13,
+ source.m20, source.m21, source.m22, source.m23,
+ source.m30, source.m31, source.m32, source.m33);
+ }
+
+
+ /**
+ * Apply a 4x4 transformation matrix.
+ */
+ public void applyMatrix(float n00, float n01, float n02, float n03,
+ float n10, float n11, float n12, float n13,
+ float n20, float n21, float n22, float n23,
+ float n30, float n31, float n32, float n33) {
+ showMissingWarning("applyMatrix");
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // MATRIX GET/SET/PRINT
+
+
+ public PMatrix getMatrix() {
+ showMissingWarning("getMatrix");
+ return null;
+ }
+
+
+ /**
+ * Copy the current transformation matrix into the specified target.
+ * Pass in null to create a new matrix.
+ */
+ public PMatrix2D getMatrix(PMatrix2D target) {
+ showMissingWarning("getMatrix");
+ return null;
+ }
+
+
+ /**
+ * Copy the current transformation matrix into the specified target.
+ * Pass in null to create a new matrix.
+ */
+ public PMatrix3D getMatrix(PMatrix3D target) {
+ showMissingWarning("getMatrix");
+ return null;
+ }
+
+
+ /**
+ * Set the current transformation matrix to the contents of another.
+ */
+ public void setMatrix(PMatrix source) {
+ if (source instanceof PMatrix2D) {
+ setMatrix((PMatrix2D) source);
+ } else if (source instanceof PMatrix3D) {
+ setMatrix((PMatrix3D) source);
+ }
+ }
+
+
+ /**
+ * Set the current transformation to the contents of the specified source.
+ */
+ public void setMatrix(PMatrix2D source) {
+ showMissingWarning("setMatrix");
+ }
+
+
+ /**
+ * Set the current transformation to the contents of the specified source.
+ */
+ public void setMatrix(PMatrix3D source) {
+ showMissingWarning("setMatrix");
+ }
+
+
+ /**
+ * Print the current model (or "transformation") matrix.
+ */
+ public void printMatrix() {
+ showMethodWarning("printMatrix");
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // CAMERA
+
+
+ public void beginCamera() {
+ showMethodWarning("beginCamera");
+ }
+
+
+ public void endCamera() {
+ showMethodWarning("endCamera");
+ }
+
+
+ public void camera() {
+ showMissingWarning("camera");
+ }
+
+
+ public void camera(float eyeX, float eyeY, float eyeZ,
+ float centerX, float centerY, float centerZ,
+ float upX, float upY, float upZ) {
+ showMissingWarning("camera");
+ }
+
+
+ public void printCamera() {
+ showMethodWarning("printCamera");
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // PROJECTION
+
+
+ public void ortho() {
+ showMissingWarning("ortho");
+ }
+
+
+ public void ortho(float left, float right,
+ float bottom, float top,
+ float near, float far) {
+ showMissingWarning("ortho");
+ }
+
+
+ public void perspective() {
+ showMissingWarning("perspective");
+ }
+
+
+ public void perspective(float fovy, float aspect, float zNear, float zFar) {
+ showMissingWarning("perspective");
+ }
+
+
+ public void frustum(float left, float right,
+ float bottom, float top,
+ float near, float far) {
+ showMethodWarning("frustum");
+ }
+
+
+ public void printProjection() {
+ showMethodWarning("printCamera");
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // SCREEN TRANSFORMS
+
+
+ /**
+ * Given an x and y coordinate, returns the x position of where
+ * that point would be placed on screen, once affected by translate(),
+ * scale(), or any other transformations.
+ */
+ public float screenX(float x, float y) {
+ showMissingWarning("screenX");
+ return 0;
+ }
+
+
+ /**
+ * Given an x and y coordinate, returns the y position of where
+ * that point would be placed on screen, once affected by translate(),
+ * scale(), or any other transformations.
+ */
+ public float screenY(float x, float y) {
+ showMissingWarning("screenY");
+ return 0;
+ }
+
+
+ /**
+ * Maps a three dimensional point to its placement on-screen.
+ * colorMode(HSB, 360, 100, 100);
+ * because the alpha values were still between 0 and 255.
+ */
+ public void colorMode(int mode, float maxX, float maxY, float maxZ) {
+ colorMode(mode, maxX, maxY, maxZ, colorModeA);
+ }
+
+
+ public void colorMode(int mode,
+ float maxX, float maxY, float maxZ, float maxA) {
+ colorMode = mode;
+
+ colorModeX = maxX; // still needs to be set for hsb
+ colorModeY = maxY;
+ colorModeZ = maxZ;
+ colorModeA = maxA;
+
+ // if color max values are all 1, then no need to scale
+ colorModeScale =
+ ((maxA != 1) || (maxX != maxY) || (maxY != maxZ) || (maxZ != maxA));
+
+ // if color is rgb/0..255 this will make it easier for the
+ // red() green() etc functions
+ colorModeDefault = (colorMode == RGB) &&
+ (colorModeA == 255) && (colorModeX == 255) &&
+ (colorModeY == 255) && (colorModeZ == 255);
+ }
+
+
+
+ //////////////////////////////////////////////////////////////
+
+ // COLOR CALCULATIONS
+
+ // Given input values for coloring, these functions will fill the calcXxxx
+ // variables with values that have been properly filtered through the
+ // current colorMode settings.
+
+ // Renderers that need to subclass any drawing properties such as fill or
+ // stroke will usally want to override methods like fillFromCalc (or the
+ // same for stroke, ambient, etc.) That way the color calcuations are
+ // covered by this based PGraphics class, leaving only a single function
+ // to override/implement in the subclass.
+
+
+ /**
+ * Set the fill to either a grayscale value or an ARGB int.
+ * size(256, 256);
+ * for (int i = 0; i < 256; i++) {
+ * color c = color(0, 0, 0, i);
+ * stroke(c);
+ * line(i, 0, i, 256);
+ * }
+ * ...on the first time through the loop, where (i == 0), since the color
+ * itself is zero (black) then it would appear indistinguishable from code
+ * that reads "fill(0)". The solution is to use the four parameter versions
+ * of stroke or fill to more directly specify the desired result.
+ */
+ protected void colorCalc(int rgb) {
+ if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) {
+ colorCalc((float) rgb);
+
+ } else {
+ colorCalcARGB(rgb, colorModeA);
+ }
+ }
+
+
+ protected void colorCalc(int rgb, float alpha) {
+ if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { // see above
+ colorCalc((float) rgb, alpha);
+
+ } else {
+ colorCalcARGB(rgb, alpha);
+ }
+ }
+
+
+ protected void colorCalc(float gray) {
+ colorCalc(gray, colorModeA);
+ }
+
+
+ protected void colorCalc(float gray, float alpha) {
+ if (gray > colorModeX) gray = colorModeX;
+ if (alpha > colorModeA) alpha = colorModeA;
+
+ if (gray < 0) gray = 0;
+ if (alpha < 0) alpha = 0;
+
+ calcR = colorModeScale ? (gray / colorModeX) : gray;
+ calcG = calcR;
+ calcB = calcR;
+ calcA = colorModeScale ? (alpha / colorModeA) : alpha;
+
+ calcRi = (int)(calcR*255); calcGi = (int)(calcG*255);
+ calcBi = (int)(calcB*255); calcAi = (int)(calcA*255);
+ calcColor = (calcAi << 24) | (calcRi << 16) | (calcGi << 8) | calcBi;
+ calcAlpha = (calcAi != 255);
+ }
+
+
+ protected void colorCalc(float x, float y, float z) {
+ colorCalc(x, y, z, colorModeA);
+ }
+
+
+ protected void colorCalc(float x, float y, float z, float a) {
+ if (x > colorModeX) x = colorModeX;
+ if (y > colorModeY) y = colorModeY;
+ if (z > colorModeZ) z = colorModeZ;
+ if (a > colorModeA) a = colorModeA;
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+ if (z < 0) z = 0;
+ if (a < 0) a = 0;
+
+ switch (colorMode) {
+ case RGB:
+ if (colorModeScale) {
+ calcR = x / colorModeX;
+ calcG = y / colorModeY;
+ calcB = z / colorModeZ;
+ calcA = a / colorModeA;
+ } else {
+ calcR = x; calcG = y; calcB = z; calcA = a;
+ }
+ break;
+
+ case HSB:
+ x /= colorModeX; // h
+ y /= colorModeY; // s
+ z /= colorModeZ; // b
+
+ calcA = colorModeScale ? (a/colorModeA) : a;
+
+ if (y == 0) { // saturation == 0
+ calcR = calcG = calcB = z;
+
+ } else {
+ float which = (x - (int)x) * 6.0f;
+ float f = which - (int)which;
+ float p = z * (1.0f - y);
+ float q = z * (1.0f - y * f);
+ float t = z * (1.0f - (y * (1.0f - f)));
+
+ switch ((int)which) {
+ case 0: calcR = z; calcG = t; calcB = p; break;
+ case 1: calcR = q; calcG = z; calcB = p; break;
+ case 2: calcR = p; calcG = z; calcB = t; break;
+ case 3: calcR = p; calcG = q; calcB = z; break;
+ case 4: calcR = t; calcG = p; calcB = z; break;
+ case 5: calcR = z; calcG = p; calcB = q; break;
+ }
+ }
+ break;
+ }
+ calcRi = (int)(255*calcR); calcGi = (int)(255*calcG);
+ calcBi = (int)(255*calcB); calcAi = (int)(255*calcA);
+ calcColor = (calcAi << 24) | (calcRi << 16) | (calcGi << 8) | calcBi;
+ calcAlpha = (calcAi != 255);
+ }
+
+
+ /**
+ * Unpacks AARRGGBB color for direct use with colorCalc.
+ *
+ * A better name? showFrame, displayable, isVisible, visible, shouldDisplay,
+ * what to call this?
+ */
+ public boolean displayable() {
+ return true;
+ }
+
+
+ /**
+ * Return true if this renderer supports 2D drawing. Defaults to true.
+ */
+ public boolean is2D() {
+ return true;
+ }
+
+
+ /**
+ * Return true if this renderer supports 2D drawing. Defaults to true.
+ */
+ public boolean is3D() {
+ return false;
+ }
+}
diff --git a/core/preproc/demo/PImage.java b/core/preproc/demo/PImage.java
new file mode 100644
index 000000000..9dd126e4b
--- /dev/null
+++ b/core/preproc/demo/PImage.java
@@ -0,0 +1,2713 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Part of the Processing project - http://processing.org
+
+ Copyright (c) 2004-08 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.awt.image.*;
+import java.io.*;
+import java.util.HashMap;
+
+import javax.imageio.ImageIO;
+
+
+/**
+ * Storage class for pixel data. This is the base class for most image and
+ * pixel information, such as PGraphics and the video library classes.
+ *
+ *
+ * All versions are RLE compressed.
+ *
+ * println(javax.imageio.ImageIO.getReaderFormatNames())
+ */
+ protected void saveImageIO(String path) throws IOException {
+ try {
+ BufferedImage bimage =
+ new BufferedImage(width, height, (format == ARGB) ?
+ BufferedImage.TYPE_INT_ARGB :
+ BufferedImage.TYPE_INT_RGB);
+ /*
+ Class bufferedImageClass =
+ Class.forName("java.awt.image.BufferedImage");
+ Constructor bufferedImageConstructor =
+ bufferedImageClass.getConstructor(new Class[] {
+ Integer.TYPE,
+ Integer.TYPE,
+ Integer.TYPE });
+ Field typeIntRgbField = bufferedImageClass.getField("TYPE_INT_RGB");
+ int typeIntRgb = typeIntRgbField.getInt(typeIntRgbField);
+ Field typeIntArgbField = bufferedImageClass.getField("TYPE_INT_ARGB");
+ int typeIntArgb = typeIntArgbField.getInt(typeIntArgbField);
+ Object bimage =
+ bufferedImageConstructor.newInstance(new Object[] {
+ new Integer(width),
+ new Integer(height),
+ new Integer((format == ARGB) ? typeIntArgb : typeIntRgb)
+ });
+ */
+
+ bimage.setRGB(0, 0, width, height, pixels, 0, width);
+ /*
+ Method setRgbMethod =
+ bufferedImageClass.getMethod("setRGB", new Class[] {
+ Integer.TYPE, Integer.TYPE,
+ Integer.TYPE, Integer.TYPE,
+ pixels.getClass(),
+ Integer.TYPE, Integer.TYPE
+ });
+ setRgbMethod.invoke(bimage, new Object[] {
+ new Integer(0), new Integer(0),
+ new Integer(width), new Integer(height),
+ pixels, new Integer(0), new Integer(width)
+ });
+ */
+
+ File file = new File(path);
+ String extension = path.substring(path.lastIndexOf('.') + 1);
+
+ ImageIO.write(bimage, extension, file);
+ /*
+ Class renderedImageClass =
+ Class.forName("java.awt.image.RenderedImage");
+ Class ioClass = Class.forName("javax.imageio.ImageIO");
+ Method writeMethod =
+ ioClass.getMethod("write", new Class[] {
+ renderedImageClass, String.class, File.class
+ });
+ writeMethod.invoke(null, new Object[] { bimage, extension, file });
+ */
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new IOException("image save failed.");
+ }
+ }
+
+
+ protected String[] saveImageFormats;
+
+ /**
+ * Save this image to disk.
+ *
+ * println(javax.imageio.ImageIO.getReaderFormatNames())
+ *