diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 96df38386..e83e443c9 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -180,71 +180,7 @@ public class Base { // setup the theme coloring fun Theme.init(); - if (Base.isMacOS()) { - String properMenuBar = "apple.laf.useScreenMenuBar"; - String menubar = Preferences.get(properMenuBar); - if (menubar != null) { - // Get the current menu bar setting and use it - System.setProperty(properMenuBar, menubar); - - } else { - // 10.4 is not affected, 10.5 (and prolly 10.6) are - if (System.getProperty("os.version").startsWith("10.4")) { - // Don't bother checking next time - Preferences.set(properMenuBar, "true"); - // Also set the menubar now - System.setProperty(properMenuBar, "true"); - - } else { - // Running 10.5 or 10.6 or whatever, give 'em the business - String warning = - "" + - "
" + - "Some menus have been disabled." + - "Due to an Apple bug, the Sketchbook and Example menus " +
- "are unusable.
" +
- "As a workaround, these items will be disabled from the " +
- "standard menu bar,
" +
- "but you can use the Open button on " +
- "the toolbar to access the same items.
" +
- "If this bug makes you sad, " +
- "please contact Apple via bugreporter.apple.com.
@@ -68,13 +70,6 @@ public class Preferences {
static final String PREFS_FILE = "preferences.txt";
- // platform strings (used to get settings for specific platforms)
-
- //static final String platforms[] = {
- // "other", "windows", "macosx", "linux"
- //};
-
-
// prompt text stuff
static final String PROMPT_YES = "Yes";
@@ -97,12 +92,6 @@ public class Preferences {
* inside a static block.
*/
static public int BUTTON_HEIGHT = 24;
- /*
- // remove this for 0121, because quaqua takes care of it
- static {
- if (Base.isMacOS()) BUTTON_HEIGHT = 29;
- }
- */
// value for the size bars, buttons, etc
@@ -125,14 +114,12 @@ public class Preferences {
JTextField sketchbookLocationField;
JCheckBox exportSeparateBox;
JCheckBox deletePreviousBox;
-// JCheckBox closingLastQuitsBox;
JCheckBox externalEditorBox;
JCheckBox memoryOverrideBox;
JTextField memoryField;
JCheckBox checkUpdatesBox;
JTextField fontSizeField;
JCheckBox autoAssociateBox;
- JCheckBox menubarWorkaroundBox;
// the calling editor, so updates can be applied
@@ -142,9 +129,9 @@ public class Preferences {
// data model
- static HashMap 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.
+ * 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.
+ *
+ * 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.
+ *
+ * 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)
+ *
+ * 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:
+ * 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.
+ *
+ * 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:
+ *
+ * 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.
+ *
+ * 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/preproc.jar b/core/preproc/preproc.jar
new file mode 100644
index 000000000..e025d6a25
Binary files /dev/null and b/core/preproc/preproc.jar differ
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)));
+ }
+}
diff --git a/core/src/processing/core/PApplet.java b/core/src/processing/core/PApplet.java
index ecbd40d98..06541c547 100644
--- a/core/src/processing/core/PApplet.java
+++ b/core/src/processing/core/PApplet.java
@@ -2687,6 +2687,12 @@ public class PApplet extends Applet
return (a > b) ? a : b;
}
+ /*
+ static public final double max(double a, double 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);
@@ -2732,6 +2738,26 @@ public class PApplet extends Applet
}
+ /**
+ * 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 double max(double[] list) {
+ if (list.length == 0) {
+ throw new ArrayIndexOutOfBoundsException(ERROR_MIN_MAX);
+ }
+ double 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;
}
@@ -2740,6 +2766,12 @@ public class PApplet extends Applet
return (a < b) ? a : b;
}
+ /*
+ static public final double min(double a, double 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);
@@ -2749,6 +2781,12 @@ public class PApplet extends Applet
return (a < b) ? ((a < c) ? a : c) : ((b < c) ? b : c);
}
+ /*
+ static public final double min(double a, double b, double c) {
+ return (a < b) ? ((a < c) ? a : c) : ((b < c) ? b : c);
+ }
+ */
+
/**
* Find the minimum value in an array.
@@ -2766,6 +2804,8 @@ public class PApplet extends Applet
}
return min;
}
+
+
/**
* Find the minimum value in an array.
* Throws an ArrayIndexOutOfBoundsException if the array is length 0.
@@ -2784,6 +2824,25 @@ public class PApplet extends Applet
}
+ /**
+ * 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 double min(double[] list) {
+ if (list.length == 0) {
+ throw new ArrayIndexOutOfBoundsException(ERROR_MIN_MAX);
+ }
+ double 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);
}
@@ -2888,11 +2947,13 @@ public class PApplet extends Applet
}
+ /*
static public final double map(double value,
double istart, double istop,
double ostart, double ostop) {
return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
}
+ */
@@ -4355,18 +4416,34 @@ public class PApplet extends Applet
* Saves bytes to a specific File location specified by the user.
*/
static public void saveBytes(File file, byte buffer[]) {
+ File tempFile = null;
try {
+ File parentDir = file.getParentFile();
+ tempFile = File.createTempFile(file.getName(), null, parentDir);
+
+ /*
String filename = file.getAbsolutePath();
createPath(filename);
OutputStream output = new FileOutputStream(file);
if (file.getName().toLowerCase().endsWith(".gz")) {
output = new GZIPOutputStream(output);
}
+ */
+ OutputStream output = createOutput(tempFile);
saveBytes(output, buffer);
output.close();
+ output = null;
+
+ if (!tempFile.renameTo(file)) {
+ System.err.println("Could not rename temporary file " +
+ tempFile.getAbsolutePath());
+ }
} catch (IOException e) {
System.err.println("error saving bytes to " + file);
+ if (tempFile != null) {
+ tempFile.delete();
+ }
e.printStackTrace();
}
}
@@ -4393,6 +4470,8 @@ public class PApplet extends Applet
static public void saveStrings(File file, String strings[]) {
+ saveStrings(createOutput(file), strings);
+ /*
try {
String location = file.getAbsolutePath();
createPath(location);
@@ -4406,18 +4485,17 @@ public class PApplet extends Applet
} catch (IOException e) {
e.printStackTrace();
}
+ */
}
static public void saveStrings(OutputStream output, String strings[]) {
- try {
- OutputStreamWriter osw = new OutputStreamWriter(output, "UTF-8");
- PrintWriter writer = new PrintWriter(osw);
+ PrintWriter writer = createWriter(output);
for (int i = 0; i < strings.length; i++) {
writer.println(strings[i]);
}
writer.flush();
- } catch (UnsupportedEncodingException e) { } // will not happen
+ writer.close();
}
diff --git a/core/src/processing/core/PGraphics.java b/core/src/processing/core/PGraphics.java
index cc5562bcd..346b11cd1 100644
--- a/core/src/processing/core/PGraphics.java
+++ b/core/src/processing/core/PGraphics.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2004-08 Ben Fry and Casey Reas
+ Copyright (c) 2004-09 Ben Fry and Casey Reas
Copyright (c) 2001-04 Massachusetts Institute of Technology
This library is free software; you can redistribute it and/or
diff --git a/core/src/processing/core/PGraphics3D.java b/core/src/processing/core/PGraphics3D.java
index ba5af3c15..57dde4343 100644
--- a/core/src/processing/core/PGraphics3D.java
+++ b/core/src/processing/core/PGraphics3D.java
@@ -358,12 +358,12 @@ public class PGraphics3D extends PGraphics {
cameraInv = new PMatrix3D();
// set up the default camera
- camera();
+// camera();
// defaults to perspective, if the user has setup up their
// own projection, they'll need to fix it after resize anyway.
// this helps the people who haven't set up their own projection.
- perspective();
+// perspective();
}
@@ -478,6 +478,12 @@ public class PGraphics3D extends PGraphics {
forwardTransform = modelview;
reverseTransform = modelviewInv;
+ // set up the default camera
+ camera();
+
+ // defaults to perspective, if the user has setup up their
+ // own projection, they'll need to fix it after resize anyway.
+ // this helps the people who haven't set up their own projection.
perspective();
// easiest for beginners
@@ -1344,7 +1350,7 @@ public class PGraphics3D extends PGraphics {
boolean bClipped = false;
int clippedCount = 0;
- cameraNear = -8;
+// cameraNear = -8;
if (vertices[a][VZ] > cameraNear) {
aClipped = true;
clippedCount++;
@@ -1358,8 +1364,15 @@ public class PGraphics3D extends PGraphics {
clippedCount++;
}
if (clippedCount == 0) {
+// if (vertices[a][VZ] < cameraFar &&
+// vertices[b][VZ] < cameraFar &&
+// vertices[c][VZ] < cameraFar) {
addTriangleWithoutClip(a, b, c);
+// }
+// } else if (true) {
+// return;
+
} else if (clippedCount == 3) {
// In this case there is only one visible point. |/|
// So we'll have to make two new points on the clip line <| |
@@ -3502,6 +3515,7 @@ public class PGraphics3D extends PGraphics {
0, y, 0, ty,
0, 0, z, tz,
0, 0, 0, 1);
+ updateProjection();
frustumMode = false;
}
@@ -3569,6 +3583,12 @@ public class PGraphics3D extends PGraphics {
0, (2*znear)/(top-bottom), (top+bottom)/(top-bottom), 0,
0, 0, -(zfar+znear)/(zfar-znear),-(2*zfar*znear)/(zfar-znear),
0, 0, -1, 0);
+ updateProjection();
+ }
+
+
+ /** Called after the 'projection' PMatrix3D has changed. */
+ protected void updateProjection() {
}
diff --git a/core/src/processing/core/PImage.java b/core/src/processing/core/PImage.java
index 9dd126e4b..c1b38f095 100644
--- a/core/src/processing/core/PImage.java
+++ b/core/src/processing/core/PImage.java
@@ -182,6 +182,7 @@ public class PImage implements PConstants, Cloneable {
raster.getDataElements(0, 0, width, height, pixels);
} else { // go the old school java 1.0 route
+// System.out.println(img.getClass().getName());
width = img.getWidth(null);
height = img.getHeight(null);
pixels = new int[width * height];
@@ -690,7 +691,7 @@ public class PImage implements PConstants, Cloneable {
throw new RuntimeException("Use filter(POSTERIZE, int levels) " +
"instead of filter(POSTERIZE)");
- case RGB:
+ case OPAQUE:
for (int i = 0; i < pixels.length; i++) {
pixels[i] |= 0xff000000;
}
diff --git a/core/src/processing/core/PVector.java b/core/src/processing/core/PVector.java
index 78209bb60..91be2f52b 100644
--- a/core/src/processing/core/PVector.java
+++ b/core/src/processing/core/PVector.java
@@ -430,6 +430,11 @@ public class PVector {
}
+ static public float dot(PVector v1, PVector v2) {
+ return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
+ }
+
+
/**
* Return a vector composed of the cross product between this and another.
*/
diff --git a/core/todo.txt b/core/todo.txt
index 988df2d09..cf4488272 100644
--- a/core/todo.txt
+++ b/core/todo.txt
@@ -1,8 +1,30 @@
-0169 core
-X remove major try/catch block from PApplet.main()
-X hopefully will allow some exception stuff to come through properly
-X PVector.angleDistance() returns NaN
-X http://dev.processing.org/bugs/show_bug.cgi?id=1316
+0171 core
+X Blurred PImages in OPENGL sketches
+X removed NPOT texture support (for further testing)
+X http://dev.processing.org/bugs/show_bug.cgi?id=1352
+
+_ open up the pdf library more (philho)
+_ http://dev.processing.org/bugs/show_bug.cgi?id=1343
+_ changing vertex alpha in P3D in a QUAD_STRIP is ignored
+_ with smoothing, it works fine, but with PTriangle, it's not
+_ smooth() not working with applets an createGraphics(JAVA2D)
+_ but works fine with applications
+_ get() with OPENGL is grabbing the wrong coords
+_ http://dev.processing.org/bugs/show_bug.cgi?id=1349
+
+_ No textures render with hint(ENABLE_ACCURATE_TEXTURES)
+_ http://dev.processing.org/bugs/show_bug.cgi?id=985
+_ need to remove the hint from the reference
+_ need to throw an error when it's used
+_ deal with issue of single pixel seam at the edge of textures
+_ http://dev.processing.org/bugs/show_bug.cgi?id=602
+_ should vertexTexture() divide by width/height or width-1/height-1?
+
+_ key and mouse events delivered out of order
+_ http://dev.processing.org/bugs/show_bug.cgi?id=638
+_ key/mouse events have concurrency problems with noLoop()
+_ http://dev.processing.org/bugs/show_bug.cgi?id=1323
+_ need to say "no drawing inside mouse/key events w/ noLoop"
_ make the index lookup use numbers up to 256?
@@ -170,12 +192,6 @@ _ smooth in P3D has zbuffer glitches
_ http://dev.processing.org/bugs/show_bug.cgi?id=1000
_ smoothing is slow
_ http://dev.processing.org/bugs/show_bug.cgi?id=1001
-_ No textures render with hint(ENABLE_ACCURATE_TEXTURES)
-_ http://dev.processing.org/bugs/show_bug.cgi?id=985
-_ need to remove the hint from the reference
-_ need to throw an error when it's used
-_ deal with issue of single pixel seam at the edge of textures
-_ ??? what is the bug # for this one?
_ textured sphere example needs to set normals
_ also needs fix for last edge and the seam
@@ -235,8 +251,6 @@ _ PApplet.main(new String[] { "classname" }) won't pass in args
_ this means that no args are after passed to the class
_ the fix would be to use the following as the call to main()
_ PApplet.main(append(new String[] { "classname }, args));
-_ key and mouse events delivered out of order
-_ http://dev.processing.org/bugs/show_bug.cgi?id=638
_ figure out why 1024x768 image takes 3.5 seconds to load
_ would using a BufferedImage work better?
_ is the image actually a BufferedImage so PixelGrabber is a waste?
+ * 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();
+ * }
+ * }
+ *
+ *
+ * Processing on multiple displays
+ *
+ *
+ */
+ public PGraphics createGraphics(int iwidth, int iheight,
+ String irenderer) {
+ PGraphics pg = makeGraphics(iwidth, iheight, irenderer, null, false);
+ //pg.parent = this; // make save() work
+ return pg;
+ }
+
+
+ /**
+ * Create an offscreen graphics surface for drawing, in this case
+ * for a renderer that writes to a file (such as PDF or DXF).
+ * @param ipath can be an absolute or relative path
+ */
+ public PGraphics createGraphics(int iwidth, int iheight,
+ String irenderer, String ipath) {
+ if (ipath != null) {
+ ipath = savePath(ipath);
+ }
+ PGraphics pg = makeGraphics(iwidth, iheight, irenderer, ipath, false);
+ pg.parent = this; // make save() work
+ return pg;
+ }
+
+
+ /**
+ * Version of createGraphics() used internally.
+ *
+ * @param ipath must be an absolute path, usually set via savePath()
+ * @oaram applet the parent applet object, this should only be non-null
+ * in cases where this is the main drawing surface object.
+ */
+ protected PGraphics makeGraphics(int iwidth, int iheight,
+ String irenderer, String ipath,
+ boolean iprimary) {
+ if (irenderer.equals(OPENGL)) {
+ if (PApplet.platform == WINDOWS) {
+ String s = System.getProperty("java.version");
+ if (s != null) {
+ if (s.equals("1.5.0_10")) {
+ System.err.println("OpenGL support is broken with Java 1.5.0_10");
+ System.err.println("See http://dev.processing.org" +
+ "/bugs/show_bug.cgi?id=513 for more info.");
+ throw new RuntimeException("Please update your Java " +
+ "installation (see bug #513)");
+ }
+ }
+ }
+ }
+
+// if (irenderer.equals(P2D)) {
+// throw new RuntimeException("The P2D renderer is currently disabled, " +
+// "please use P3D or JAVA2D.");
+// }
+
+ String openglError =
+ "Before using OpenGL, first select " +
+ "Import Library > opengl from the Sketch menu.";
+
+ try {
+ /*
+ Class> rendererClass = Class.forName(irenderer);
+
+ Class> constructorParams[] = null;
+ Object constructorValues[] = null;
+
+ if (ipath == null) {
+ constructorParams = new Class[] {
+ Integer.TYPE, Integer.TYPE, PApplet.class
+ };
+ constructorValues = new Object[] {
+ new Integer(iwidth), new Integer(iheight), this
+ };
+ } else {
+ constructorParams = new Class[] {
+ Integer.TYPE, Integer.TYPE, PApplet.class, String.class
+ };
+ constructorValues = new Object[] {
+ new Integer(iwidth), new Integer(iheight), this, ipath
+ };
+ }
+
+ Constructor> constructor =
+ rendererClass.getConstructor(constructorParams);
+ PGraphics pg = (PGraphics) constructor.newInstance(constructorValues);
+ */
+
+ Class> rendererClass =
+ Thread.currentThread().getContextClassLoader().loadClass(irenderer);
+
+ //Class> params[] = null;
+ //PApplet.println(rendererClass.getConstructors());
+ Constructor> constructor = rendererClass.getConstructor(new Class[] { });
+ PGraphics pg = (PGraphics) constructor.newInstance();
+
+ pg.setParent(this);
+ pg.setPrimary(iprimary);
+ if (ipath != null) pg.setPath(ipath);
+ pg.setSize(iwidth, iheight);
+
+ // everything worked, return it
+ return pg;
+
+ } catch (InvocationTargetException ite) {
+ String msg = ite.getTargetException().getMessage();
+ if ((msg != null) &&
+ (msg.indexOf("no jogl in java.library.path") != -1)) {
+ throw new RuntimeException(openglError +
+ " (The native library is missing.)");
+
+ } else {
+ ite.getTargetException().printStackTrace();
+ Throwable target = ite.getTargetException();
+ if (platform == MACOSX) target.printStackTrace(System.out); // bug
+ // neither of these help, or work
+ //target.printStackTrace(System.err);
+ //System.err.flush();
+ //System.out.println(System.err); // and the object isn't null
+ throw new RuntimeException(target.getMessage());
+ }
+
+ } catch (ClassNotFoundException cnfe) {
+ if (cnfe.getMessage().indexOf("processing.opengl.PGraphicsGL") != -1) {
+ throw new RuntimeException(openglError +
+ " (The library .jar file is missing.)");
+ } else {
+ throw new RuntimeException("You need to use \"Import Library\" " +
+ "to add " + irenderer + " to your sketch.");
+ }
+
+ } catch (Exception e) {
+ //System.out.println("ex3");
+ if ((e instanceof IllegalArgumentException) ||
+ (e instanceof NoSuchMethodException) ||
+ (e instanceof IllegalAccessException)) {
+ e.printStackTrace();
+ /*
+ String msg = "public " +
+ irenderer.substring(irenderer.lastIndexOf('.') + 1) +
+ "(int width, int height, PApplet parent" +
+ ((ipath == null) ? "" : ", String filename") +
+ ") does not exist.";
+ */
+ String msg = irenderer + " needs to be updated " +
+ "for the current release of Processing.";
+ throw new RuntimeException(msg);
+
+ } else {
+ if (platform == MACOSX) e.printStackTrace(System.out);
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+ }
+
+
+ /**
+ * Preferred method of creating new PImage objects, ensures that a
+ * reference to the parent PApplet is included, which makes save() work
+ * without needing an absolute path.
+ */
+ public PImage createImage(int wide, int high, int format) {
+ PImage image = new PImage(wide, high, format);
+ image.parent = this; // make save() work
+ return image;
+ }
+
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
+ public void update(Graphics screen) {
+ paint(screen);
+ }
+
+
+ //synchronized public void paint(Graphics screen) { // shutting off for 0146
+ public void paint(Graphics screen) {
+ // ignore the very first call to paint, since it's coming
+ // from the o.s., and the applet will soon update itself anyway.
+ if (frameCount == 0) {
+// println("Skipping frame");
+ // paint() may be called more than once before things
+ // are finally painted to the screen and the thread gets going
+ return;
+ }
+
+ // without ignoring the first call, the first several frames
+ // are confused because paint() gets called in the midst of
+ // the initial nextFrame() call, so there are multiple
+ // updates fighting with one another.
+
+ // g.image is synchronized so that draw/loop and paint don't
+ // try to fight over it. this was causing a randomized slowdown
+ // that would cut the frameRate into a third on macosx,
+ // and is probably related to the windows sluggishness bug too
+
+ // make sure the screen is visible and usable
+ // (also prevents over-drawing when using PGraphicsOpenGL)
+ if ((g != null) && (g.image != null)) {
+// println("inside paint(), screen.drawImage()");
+ screen.drawImage(g.image, 0, 0, null);
+ }
+ }
+
+
+ // active paint method
+ protected void paint() {
+ try {
+ Graphics screen = this.getGraphics();
+ if (screen != null) {
+ if ((g != null) && (g.image != null)) {
+ screen.drawImage(g.image, 0, 0, null);
+ }
+ Toolkit.getDefaultToolkit().sync();
+ }
+ } catch (Exception e) {
+ // Seen on applet destroy, maybe can ignore?
+ e.printStackTrace();
+
+// } finally {
+// if (g != null) {
+// g.dispose();
+// }
+ }
+ }
+
+
+ //////////////////////////////////////////////////////////////
+
+
+ /**
+ * Main method for the primary animation thread.
+ *
+ * Painting in AWT and Swing
+ */
+ public void run() { // not good to make this synchronized, locks things up
+ long beforeTime = System.nanoTime();
+ long overSleepTime = 0L;
+
+ int noDelays = 0;
+ // Number of frames with a delay of 0 ms before the
+ // animation thread yields to other running threads.
+ final int NO_DELAYS_PER_YIELD = 15;
+
+ /*
+ // this has to be called after the exception is thrown,
+ // otherwise the supporting libs won't have a valid context to draw to
+ Object methodArgs[] =
+ new Object[] { new Integer(width), new Integer(height) };
+ sizeMethods.handle(methodArgs);
+ */
+
+ while ((Thread.currentThread() == thread) && !finished) {
+ // Don't resize the renderer from the EDT (i.e. from a ComponentEvent),
+ // otherwise it may attempt a resize mid-render.
+ if (resizeRequest) {
+ resizeRenderer(resizeWidth, resizeHeight);
+ resizeRequest = false;
+ }
+
+ // render a single frame
+ handleDraw();
+
+ if (frameCount == 1) {
+ // Call the request focus event once the image is sure to be on
+ // screen and the component is valid. The OpenGL renderer will
+ // request focus for its canvas inside beginDraw().
+ // http://java.sun.com/j2se/1.4.2/docs/api/java/awt/doc-files/FocusSpec.html
+ //println("requesting focus");
+ requestFocus();
+ }
+
+ // wait for update & paint to happen before drawing next frame
+ // this is necessary since the drawing is sometimes in a
+ // separate thread, meaning that the next frame will start
+ // before the update/paint is completed
+
+ long afterTime = System.nanoTime();
+ long timeDiff = afterTime - beforeTime;
+ //System.out.println("time diff is " + timeDiff);
+ long sleepTime = (frameRatePeriod - timeDiff) - overSleepTime;
+
+ if (sleepTime > 0) { // some time left in this cycle
+ try {
+// Thread.sleep(sleepTime / 1000000L); // nanoseconds -> milliseconds
+ Thread.sleep(sleepTime / 1000000L, (int) (sleepTime % 1000000L));
+ noDelays = 0; // Got some sleep, not delaying anymore
+ } catch (InterruptedException ex) { }
+
+ overSleepTime = (System.nanoTime() - afterTime) - sleepTime;
+ //System.out.println(" oversleep is " + overSleepTime);
+
+ } else { // sleepTime <= 0; the frame took longer than the period
+// excess -= sleepTime; // store excess time value
+ overSleepTime = 0L;
+
+ if (noDelays > NO_DELAYS_PER_YIELD) {
+ Thread.yield(); // give another thread a chance to run
+ noDelays = 0;
+ }
+ }
+
+ beforeTime = System.nanoTime();
+ }
+
+ stop(); // call to shutdown libs?
+
+ // If the user called the exit() function, the window should close,
+ // rather than the sketch just halting.
+ if (exitCalled) {
+ exit2();
+ }
+ }
+
+
+ //synchronized public void handleDisplay() {
+ public void handleDraw() {
+ if (g != null && (looping || redraw)) {
+ if (!g.canDraw()) {
+ // Don't draw if the renderer is not yet ready.
+ // (e.g. OpenGL has to wait for a peer to be on screen)
+ return;
+ }
+
+ //System.out.println("handleDraw() " + frameCount);
+
+ g.beginDraw();
+ if (recorder != null) {
+ recorder.beginDraw();
+ }
+
+ long now = System.nanoTime();
+
+ if (frameCount == 0) {
+ try {
+ //println("Calling setup()");
+ setup();
+ //println("Done with setup()");
+
+ } catch (RendererChangeException e) {
+ // Give up, instead set the new renderer and re-attempt setup()
+ return;
+ }
+ this.defaultSize = false;
+
+ } else { // frameCount > 0, meaning an actual draw()
+ // update the current frameRate
+ double rate = 1000000.0 / ((now - frameRateLastNanos) / 1000000.0);
+ float instantaneousRate = (float) rate / 1000.0f;
+ frameRate = (frameRate * 0.9f) + (instantaneousRate * 0.1f);
+
+ preMethods.handle();
+
+ // use dmouseX/Y as previous mouse pos, since this is the
+ // last position the mouse was in during the previous draw.
+ pmouseX = dmouseX;
+ pmouseY = dmouseY;
+
+ //println("Calling draw()");
+ draw();
+ //println("Done calling draw()");
+
+ // dmouseX/Y is updated only once per frame (unlike emouseX/Y)
+ dmouseX = mouseX;
+ dmouseY = mouseY;
+
+ // these are called *after* loop so that valid
+ // drawing commands can be run inside them. it can't
+ // be before, since a call to background() would wipe
+ // out anything that had been drawn so far.
+ dequeueMouseEvents();
+ dequeueKeyEvents();
+
+ drawMethods.handle();
+
+ redraw = false; // unset 'redraw' flag in case it was set
+ // (only do this once draw() has run, not just setup())
+
+ }
+
+ g.endDraw();
+ if (recorder != null) {
+ recorder.endDraw();
+ }
+
+ frameRateLastNanos = now;
+ frameCount++;
+
+ // Actively render the screen
+ paint();
+
+// repaint();
+// getToolkit().sync(); // force repaint now (proper method)
+
+ postMethods.handle();
+ }
+ }
+
+
+ //////////////////////////////////////////////////////////////
+
+
+
+ synchronized public void redraw() {
+ if (!looping) {
+ redraw = true;
+// if (thread != null) {
+// // wake from sleep (necessary otherwise it'll be
+// // up to 10 seconds before update)
+// if (CRUSTY_THREADS) {
+// thread.interrupt();
+// } else {
+// synchronized (blocker) {
+// blocker.notifyAll();
+// }
+// }
+// }
+ }
+ }
+
+
+ synchronized public void loop() {
+ if (!looping) {
+ looping = true;
+ }
+ }
+
+
+ synchronized public void noLoop() {
+ if (looping) {
+ looping = false;
+ }
+ }
+
+
+ //////////////////////////////////////////////////////////////
+
+
+ public void addListeners() {
+ addMouseListener(this);
+ addMouseMotionListener(this);
+ addKeyListener(this);
+ addFocusListener(this);
+
+ addComponentListener(new ComponentAdapter() {
+ public void componentResized(ComponentEvent e) {
+ Component c = e.getComponent();
+ //System.out.println("componentResized() " + c);
+ Rectangle bounds = c.getBounds();
+ resizeRequest = true;
+ resizeWidth = bounds.width;
+ resizeHeight = bounds.height;
+ }
+ });
+ }
+
+
+ //////////////////////////////////////////////////////////////
+
+
+ MouseEvent mouseEventQueue[] = new MouseEvent[10];
+ int mouseEventCount;
+
+ protected void enqueueMouseEvent(MouseEvent e) {
+ synchronized (mouseEventQueue) {
+ if (mouseEventCount == mouseEventQueue.length) {
+ MouseEvent temp[] = new MouseEvent[mouseEventCount << 1];
+ System.arraycopy(mouseEventQueue, 0, temp, 0, mouseEventCount);
+ mouseEventQueue = temp;
+ }
+ mouseEventQueue[mouseEventCount++] = e;
+ }
+ }
+
+ protected void dequeueMouseEvents() {
+ synchronized (mouseEventQueue) {
+ for (int i = 0; i < mouseEventCount; i++) {
+ mouseEvent = mouseEventQueue[i];
+ handleMouseEvent(mouseEvent);
+ }
+ mouseEventCount = 0;
+ }
+ }
+
+
+ /**
+ * Actually take action based on a mouse event.
+ * Internally updates mouseX, mouseY, mousePressed, and mouseEvent.
+ * Then it calls the event type with no params,
+ * i.e. mousePressed() or mouseReleased() that the user may have
+ * overloaded to do something more useful.
+ */
+ protected void handleMouseEvent(MouseEvent event) {
+ int id = event.getID();
+
+ // http://dev.processing.org/bugs/show_bug.cgi?id=170
+ // also prevents mouseExited() on the mac from hosing the mouse
+ // position, because x/y are bizarre values on the exit event.
+ // see also the id check below.. both of these go together
+ if ((id == MouseEvent.MOUSE_DRAGGED) ||
+ (id == MouseEvent.MOUSE_MOVED)) {
+ pmouseX = emouseX;
+ pmouseY = emouseY;
+ mouseX = event.getX();
+ mouseY = event.getY();
+ }
+
+ mouseEvent = event;
+
+ int modifiers = event.getModifiers();
+ if ((modifiers & InputEvent.BUTTON1_MASK) != 0) {
+ mouseButton = LEFT;
+ } else if ((modifiers & InputEvent.BUTTON2_MASK) != 0) {
+ mouseButton = CENTER;
+ } else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) {
+ mouseButton = RIGHT;
+ }
+ // if running on macos, allow ctrl-click as right mouse
+ if (platform == MACOSX) {
+ if (mouseEvent.isPopupTrigger()) {
+ mouseButton = RIGHT;
+ }
+ }
+
+ mouseEventMethods.handle(new Object[] { event });
+
+ // this used to only be called on mouseMoved and mouseDragged
+ // change it back if people run into trouble
+ if (firstMouse) {
+ pmouseX = mouseX;
+ pmouseY = mouseY;
+ dmouseX = mouseX;
+ dmouseY = mouseY;
+ firstMouse = false;
+ }
+
+ //println(event);
+
+ switch (id) {
+ case MouseEvent.MOUSE_PRESSED:
+ mousePressed = true;
+ mousePressed();
+ break;
+ case MouseEvent.MOUSE_RELEASED:
+ mousePressed = false;
+ mouseReleased();
+ break;
+ case MouseEvent.MOUSE_CLICKED:
+ mouseClicked();
+ break;
+ case MouseEvent.MOUSE_DRAGGED:
+ mouseDragged();
+ break;
+ case MouseEvent.MOUSE_MOVED:
+ mouseMoved();
+ break;
+ }
+
+ if ((id == MouseEvent.MOUSE_DRAGGED) ||
+ (id == MouseEvent.MOUSE_MOVED)) {
+ emouseX = mouseX;
+ emouseY = mouseY;
+ }
+ }
+
+
+ /**
+ * Figure out how to process a mouse event. When loop() has been
+ * called, the events will be queued up until drawing is complete.
+ * If noLoop() has been called, then events will happen immediately.
+ */
+ protected void checkMouseEvent(MouseEvent event) {
+ if (looping) {
+ enqueueMouseEvent(event);
+ } else {
+ handleMouseEvent(event);
+ }
+ }
+
+
+ /**
+ * If you override this or any function that takes a "MouseEvent e"
+ * without calling its super.mouseXxxx() then mouseX, mouseY,
+ * mousePressed, and mouseEvent will no longer be set.
+ */
+ public void mousePressed(MouseEvent e) {
+ checkMouseEvent(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ checkMouseEvent(e);
+ }
+
+ public void mouseClicked(MouseEvent e) {
+ checkMouseEvent(e);
+ }
+
+ public void mouseEntered(MouseEvent e) {
+ checkMouseEvent(e);
+ }
+
+ public void mouseExited(MouseEvent e) {
+ checkMouseEvent(e);
+ }
+
+ public void mouseDragged(MouseEvent e) {
+ checkMouseEvent(e);
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ checkMouseEvent(e);
+ }
+
+
+ /**
+ * Mouse has been pressed, and should be considered "down"
+ * until mouseReleased() is called. If you must, use
+ * int button = mouseEvent.getButton();
+ * to figure out which button was clicked. It will be one of:
+ * MouseEvent.BUTTON1, MouseEvent.BUTTON2, MouseEvent.BUTTON3
+ * Note, however, that this is completely inconsistent across
+ * platforms.
+ */
+ public void mousePressed() { }
+
+ /**
+ * Mouse button has been released.
+ */
+ public void mouseReleased() { }
+
+ /**
+ * When the mouse is clicked, mousePressed() will be called,
+ * then mouseReleased(), then mouseClicked(). Note that
+ * mousePressed is already false inside of mouseClicked().
+ */
+ public void mouseClicked() { }
+
+ /**
+ * Mouse button is pressed and the mouse has been dragged.
+ */
+ public void mouseDragged() { }
+
+ /**
+ * Mouse button is not pressed but the mouse has changed locations.
+ */
+ public void mouseMoved() { }
+
+
+ //////////////////////////////////////////////////////////////
+
+
+ KeyEvent keyEventQueue[] = new KeyEvent[10];
+ int keyEventCount;
+
+ protected void enqueueKeyEvent(KeyEvent e) {
+ synchronized (keyEventQueue) {
+ if (keyEventCount == keyEventQueue.length) {
+ KeyEvent temp[] = new KeyEvent[keyEventCount << 1];
+ System.arraycopy(keyEventQueue, 0, temp, 0, keyEventCount);
+ keyEventQueue = temp;
+ }
+ keyEventQueue[keyEventCount++] = e;
+ }
+ }
+
+ protected void dequeueKeyEvents() {
+ synchronized (keyEventQueue) {
+ for (int i = 0; i < keyEventCount; i++) {
+ keyEvent = keyEventQueue[i];
+ handleKeyEvent(keyEvent);
+ }
+ keyEventCount = 0;
+ }
+ }
+
+
+ protected void handleKeyEvent(KeyEvent event) {
+ keyEvent = event;
+ key = event.getKeyChar();
+ keyCode = event.getKeyCode();
+
+ keyEventMethods.handle(new Object[] { event });
+
+ switch (event.getID()) {
+ case KeyEvent.KEY_PRESSED:
+ keyPressed = true;
+ keyPressed();
+ break;
+ case KeyEvent.KEY_RELEASED:
+ keyPressed = false;
+ keyReleased();
+ break;
+ case KeyEvent.KEY_TYPED:
+ keyTyped();
+ break;
+ }
+
+ // if someone else wants to intercept the key, they should
+ // set key to zero (or something besides the ESC).
+ if (event.getID() == KeyEvent.KEY_PRESSED) {
+ if (key == KeyEvent.VK_ESCAPE) {
+ exit();
+ }
+ // When running tethered to the Processing application, respond to
+ // Ctrl-W (or Cmd-W) events by closing the sketch. Disable this behavior
+ // when running independently, because this sketch may be one component
+ // embedded inside an application that has its own close behavior.
+ if (external &&
+ event.getModifiers() == MENU_SHORTCUT &&
+ event.getKeyCode() == 'W') {
+ exit();
+ }
+ }
+ }
+
+
+ protected void checkKeyEvent(KeyEvent event) {
+ if (looping) {
+ enqueueKeyEvent(event);
+ } else {
+ handleKeyEvent(event);
+ }
+ }
+
+
+ /**
+ * Overriding keyXxxxx(KeyEvent e) functions will cause the 'key',
+ * 'keyCode', and 'keyEvent' variables to no longer work;
+ * key events will no longer be queued until the end of draw();
+ * and the keyPressed(), keyReleased() and keyTyped() methods
+ * will no longer be called.
+ */
+ public void keyPressed(KeyEvent e) { checkKeyEvent(e); }
+ public void keyReleased(KeyEvent e) { checkKeyEvent(e); }
+ public void keyTyped(KeyEvent e) { checkKeyEvent(e); }
+
+
+ /**
+ * Called each time a single key on the keyboard is pressed.
+ * Because of how operating systems handle key repeats, holding
+ * down a key will cause multiple calls to keyPressed(), because
+ * the OS repeat takes over.
+ *
+ *
+ * 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");
+ * }
+ *
+ *
+ *
+ * 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.
+ *
+ * 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).
+ *
+ * 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.
+ * 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.
+ *
+ * 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
+ *
+ *
+ * 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())
+ *