/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Part of the Processing project - http://processing.org
Copyright (c) 2012-22 The Processing Foundation
Copyright (c) 2004-12 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, version 2.1.
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.io.*;
import java.lang.reflect.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.text.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.regex.*;
import java.util.zip.*;
// loadXML() error handling
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
// TODO have this removed by 4.0 final
import processing.awt.ShimAWT;
import processing.data.*;
import processing.event.*;
import processing.opengl.*;
/**
* Base class for all sketches that use processing.core.
*
* The
* Window Size and Full Screen page on the Wiki has useful information
* about sizing, multiple displays, full screen, etc.
*
* Processing uses active mode rendering. All animation tasks happen on the
* "Processing Animation Thread". The setup() and draw() methods are handled
* by that thread, and events (like mouse movement and key presses, which are
* fired by the event dispatch thread or EDT) are queued to be safely handled
* at the end of draw().
*
* Starting with 3.0a6, blit operations are on the EDT, so as not to cause
* GUI problems with Swing and AWT. In the case of the default renderer, the
* sketch renders to an offscreen image, then the EDT is asked to bring that
* image to the screen.
*
* For code that needs to run on the EDT, use EventQueue.invokeLater(). When
* doing so, be careful to synchronize between that code and the Processing
* animation thread. That is, you can't call Processing methods from the EDT
* or at any random time from another thread. Use of a callback function or
* the registerXxx() methods in PApplet can help ensure that your code doesn't
* do something naughty.
*
* As of Processing 3.0, we have removed Applet as the base class for PApplet.
* This means that we can remove lots of legacy code, however one downside is
* that it's no longer possible (without extra code) to embed a PApplet into
* another Java application.
*
* As of Processing 3.0, we have discontinued support for versions of Java
* prior to 1.8. We don't have enough people to support it, and for a
* project of our (tiny) size, we should be focusing on the future, rather
* than working around legacy Java code.
*/
@SuppressWarnings({"unused", "FinalStaticMethod", "ManualMinMaxCalculation"})
public class PApplet implements PConstants {
//public class PApplet extends PSketch { // possible in the next alpha
/** Full name of the Java version (i.e. 1.5.0_11). */
static public final String javaVersionName =
System.getProperty("java.version");
static public final int javaPlatform;
static {
String version = javaVersionName;
if (javaVersionName.startsWith("1.")) {
version = version.substring(2);
javaPlatform = parseInt(version.substring(0, version.indexOf('.')));
} else {
// Remove -xxx and .yyy from java.version (@see JEP-223)
javaPlatform = parseInt(version.replaceAll("-.*","").replaceAll("\\..*",""));
}
}
/**
* Do not use; javaPlatform or javaVersionName are better options.
* For instance, javaPlatform is useful when you need a number for
* comparison, i.e. "if (javaPlatform >= 9)".
*/
@Deprecated
public static final float javaVersion = 1 + javaPlatform / 10f;
/**
* Current platform in use, one of the PConstants WINDOWS, MACOS, LINUX or OTHER.
*/
static public int platform;
static {
final String name = System.getProperty("os.name");
if (name.contains("Mac")) {
platform = MACOS;
} else if (name.contains("Windows")) {
platform = WINDOWS;
} else if (name.equals("Linux")) { // true for the ibm vm
platform = LINUX;
} else {
platform = OTHER;
}
}
/**
* Whether to use native (AWT) dialogs for selectInput and selectOutput.
* The native dialogs on some platforms can be ugly, buggy, or missing
* features. For 3.3.5, this defaults to true on all platforms.
*/
static public boolean useNativeSelect = true;
/** The PGraphics renderer associated with this PApplet */
public PGraphics g;
/**
* System variable that stores the width of the computer screen.
* For example, if the current screen resolution is 1920x1080,
*
displayWidth is 1920 and
displayHeight is 1080.
*
* @webref environment
* @webBrief Variable that stores the width of the computer screen
* @see PApplet#displayHeight
* @see PApplet#size(int, int)
*/
public int displayWidth;
/**
* System variable that stores the height of the computer screen.
* For example, if the current screen resolution is 1920x1080,
*
displayWidth is 1920 and
displayHeight is 1080.
*
* @webref environment
* @webBrief Variable that stores the height of the computer screen
* @see PApplet#displayWidth
* @see PApplet#size(int, int)
*/
public int displayHeight;
public int windowX;
public int windowY;
/** 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.
* @see PApplet#main
*/
public String[] args;
/**
* Path to sketch folder. Previously undocumented, and made private
* in 3.0 alpha 5 so that people use the sketchPath() method which
* will initialize it properly. Call sketchPath() once to set it.
*/
private String sketchPath;
static final boolean DEBUG = false;
// static final boolean DEBUG = true;
/** Default width and height for sketch when not specified */
static public final int DEFAULT_WIDTH = 100;
static public final int DEFAULT_HEIGHT = 100;
/**
* The
pixels[] array contains the values for all the pixels in the
* display window. These values are of the color datatype. This array is
* defined by the size of the display window. For example, if the window is
* 100 x 100 pixels, there will be 10,000 values and if the window is
* 200 x 300 pixels, there will be 60,000 values. When the pixel density is
* set to higher than 1 with the
pixelDensity() function, these values
* will change. See the reference for
pixelWidth or
pixelHeight
* for more information.
*
* Before accessing this array, the data must be loaded with the
loadPixels()
* function. Failure to do so may result in a NullPointerException. Subsequent
* changes to the display window will not be reflected in
pixels until
*
loadPixels() is called again. After
pixels has been modified,
* the
updatePixels() function must be run to update the content of the
* display window.
*
* @webref image:pixels
* @webBrief Array containing the values for all the pixels in the display window
* @see PApplet#loadPixels()
* @see PApplet#updatePixels()
* @see PApplet#get(int, int, int, int)
* @see PApplet#set(int, int, int)
* @see PImage
* @see PApplet#pixelDensity(int)
* @see PApplet#pixelWidth
* @see PApplet#pixelHeight
*/
public int[] pixels;
/**
*
* System variable which stores the width of the display window. This value
* is set by the first parameter of the
size() function. For
* example, the function call
size(320, 240) sets the
width
* variable to the value 320. The value of
width defaults to 100 if
*
size() is not used in a program.
*
* @webref environment
* @webBrief System variable which stores the width of the display window
* @see PApplet#height
* @see PApplet#size(int, int)
*/
public int width = DEFAULT_WIDTH;
/**
*
* System variable which stores the height of the display window. This
* value is set by the second parameter of the
size() function. For
* example, the function call
size(320, 240) sets the
height
* variable to the value 240. The value of
height defaults to 100 if
*
size() is not used in a program.
*
* @webref environment
* @webBrief System variable which stores the height of the display window
* @see PApplet#width
* @see PApplet#size(int, int)
*/
public int height = DEFAULT_HEIGHT;
/**
*
* When
pixelDensity(2) is used to make use of a high resolution
* display (called a Retina display on OS X or high-dpi on Windows and
* Linux), the width and height of the sketch do not change, but the
* number of pixels is doubled. As a result, all operations that use pixels
* (like
loadPixels(),
get(),
set(), etc.) happen
* in this doubled space. As a convenience, the variables
pixelWidth
* and
pixelHeight hold the actual width and height of the sketch
* in pixels. This is useful for any sketch that uses the
pixels[]
* array, for instance, because the number of elements in the array will
* be
pixelWidth*pixelHeight, not
width*height.
*
* @webref environment
* @webBrief The actual pixel width when using high resolution display
* @see PApplet#pixelHeight
* @see #pixelDensity(int)
* @see #displayDensity()
*/
public int pixelWidth;
/**
* When
pixelDensity(2) is used to make use of a high resolution
* display (called a Retina display on OS X or high-dpi on Windows and
* Linux), the width and height of the sketch do not change, but the
* number of pixels is doubled. As a result, all operations that use pixels
* (like
loadPixels(),
get(),
set(), etc.) happen
* in this doubled space. As a convenience, the variables
pixelWidth
* and
pixelHeight hold the actual width and height of the sketch
* in pixels. This is useful for any sketch that uses the
pixels[]
* array, for instance, because the number of elements in the array will
* be
pixelWidth*pixelHeight, not
width*height.
*
* @webref environment
* @webBrief The actual pixel height when using high resolution display
* @see PApplet#pixelWidth
* @see #pixelDensity(int)
* @see #displayDensity()
*/
public int pixelHeight;
// Making this private until we have a compelling reason to make it public.
// Seems problematic/weird for it to be possible to set windowRatio = false
// relative to how other API works. And not sure what the use case would be.
private boolean windowRatio;
/**
* Version of mouseX/mouseY to use with windowRatio().
*/
public int rmouseX;
public int rmouseY;
/**
* Version of width/height to use with windowRatio().
*/
public int rwidth;
public int rheight;
/** Offset from left when windowRatio is in use. */
public float ratioLeft;
/** Offset from the top when windowRatio is in use. */
public float ratioTop;
/** Amount of scaling to be applied for the window ratio. */
public float ratioScale;
/**
* Keeps track of ENABLE_KEY_REPEAT hint
*/
protected boolean keyRepeatEnabled = false;
/**
* The system variable
mouseX always contains the current horizontal
* coordinate of the mouse.
*
* Note that Processing can only track the mouse position when the pointer
* is over the current window. The default value of
mouseX is
0,
* so
0 will be returned until the mouse moves in front of the sketch
* window. (This typically happens when a sketch is first run.) Once the
* mouse moves away from the window,
mouseX will continue to report
* its most recent position.
*
* @webref input:mouse
* @webBrief The system variable that always contains the current horizontal coordinate of the mouse
* @see PApplet#mouseY
* @see PApplet#pmouseX
* @see PApplet#pmouseY
* @see PApplet#mousePressed
* @see PApplet#mousePressed()
* @see PApplet#mouseReleased()
* @see PApplet#mouseClicked()
* @see PApplet#mouseMoved()
* @see PApplet#mouseDragged()
* @see PApplet#mouseButton
* @see PApplet#mouseWheel(MouseEvent)
*/
public int mouseX;
/**
* The system variable
mouseY always contains the current
* vertical coordinate of the mouse.
*
* Note that Processing can only track the mouse position when the pointer
* is over the current window. The default value of
mouseY is
0,
* so
0 will be returned until the mouse moves in front of the sketch
* window. (This typically happens when a sketch is first run.) Once the
* mouse moves away from the window,
mouseY will continue to report
* its most recent position.
*
* @webref input:mouse
* @webBrief The system variable that always contains the current vertical coordinate of the mouse
* @see PApplet#mouseX
* @see PApplet#pmouseX
* @see PApplet#pmouseY
* @see PApplet#mousePressed
* @see PApplet#mousePressed()
* @see PApplet#mouseReleased()
* @see PApplet#mouseClicked()
* @see PApplet#mouseMoved()
* @see PApplet#mouseDragged()
* @see PApplet#mouseButton
* @see PApplet#mouseWheel(MouseEvent)
*
*/
public int mouseY;
/**
* The system variable
pmouseX always contains the horizontal
* position of the mouse in the frame previous to the current frame.
*
* You may find that
pmouseX and
pmouseY have different values
* when referenced inside of
draw() and inside of mouse events like
*
mousePressed() and
mouseMoved(). Inside
draw(),
*
pmouseX and
pmouseY update only once per frame (once per trip
* through the
draw() loop). But inside mouse events, they update each
* time the event is called. If these values weren't updated immediately during
* events, then the mouse position would be read only once per frame, resulting
* in slight delays and choppy interaction. If the mouse variables were always
* updated multiple times per frame, then something like
line(pmouseX, pmouseY,
* mouseX, mouseY) inside
draw() would have lots of gaps, because
*
pmouseX may have changed several times in between the calls to
*
line().
* If you want values relative to the previous frame, use
pmouseX and
*
pmouseY inside
draw(). If you want continuous response, use
*
pmouseX and
pmouseY inside the mouse event functions.
*
* @webref input:mouse
* @webBrief The system variable that always contains the horizontal
* position of the mouse in the frame previous to the current frame
* @see PApplet#mouseX
* @see PApplet#mouseY
* @see PApplet#pmouseY
* @see PApplet#mousePressed
* @see PApplet#mousePressed()
* @see PApplet#mouseReleased()
* @see PApplet#mouseClicked()
* @see PApplet#mouseMoved()
* @see PApplet#mouseDragged()
* @see PApplet#mouseButton
* @see PApplet#mouseWheel(MouseEvent)
*/
public int pmouseX;
/**
* The system variable
pmouseY always contains the vertical position
* of the mouse in the frame previous to the current frame. More detailed
* information about how
pmouseY is updated inside of
draw()
* and mouse events is explained in the reference for
pmouseX.
*
* @webref input:mouse
* @webBrief The system variable that always contains the vertical position
* of the mouse in the frame previous to the current frame
* @see PApplet#mouseX
* @see PApplet#mouseY
* @see PApplet#pmouseX
* @see PApplet#mousePressed
* @see PApplet#mousePressed()
* @see PApplet#mouseReleased()
* @see PApplet#mouseClicked()
* @see PApplet#mouseMoved()
* @see PApplet#mouseDragged()
* @see PApplet#mouseButton
* @see PApplet#mouseWheel(MouseEvent)
*/
public int pmouseY;
/**
* Previous mouseX/Y for the draw loop, separated out because this is
* separate from the pmouseX/Y when inside the mouse event handlers.
* See emouseX/Y for an explanation.
*/
protected int dmouseX, dmouseY;
/**
* The 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.
*
* @deprecated Please refrain from using this variable, it will be removed
* from future releases of Processing because it cannot be used consistently
* across platforms and input methods.
*/
@Deprecated
public boolean firstMouse = true;
/**
* When a mouse button is pressed, the value of the system variable
* mouseButton is set to either LEFT, RIGHT, or
* CENTER, depending on which button is pressed. (If no button is
* pressed, mouseButton may be reset to 0. For that reason,
* it's best to use mousePressed first to test if any button is being
* pressed, and only then test the value of mouseButton, as shown in
* the examples above.)
*
*
Advanced:
*
* If running on macOS, a ctrl-click will be interpreted as the right-hand
* mouse button (unlike Java, which reports it as the left mouse).
* @webref input:mouse
* @webBrief Shows which mouse button is pressed
* @see PApplet#mouseX
* @see PApplet#mouseY
* @see PApplet#pmouseX
* @see PApplet#pmouseY
* @see PApplet#mousePressed
* @see PApplet#mousePressed()
* @see PApplet#mouseReleased()
* @see PApplet#mouseClicked()
* @see PApplet#mouseMoved()
* @see PApplet#mouseDragged()
* @see PApplet#mouseWheel(MouseEvent)
*/
public int mouseButton;
/**
* The
mousePressed variable stores whether a mouse button has been pressed.
* The
mouseButton variable (see the related reference entry) can be used to
* determine which button has been pressed.
*
* Mouse and keyboard events only work when a program has
draw().
* Without
draw(), the code is only run once and then stops
* listening for events.
*
* @webref input:mouse
* @webBrief Variable storing if a mouse button is pressed
* @see PApplet#mouseX
* @see PApplet#mouseY
* @see PApplet#pmouseX
* @see PApplet#pmouseY
* @see PApplet#mousePressed()
* @see PApplet#mouseReleased()
* @see PApplet#mouseClicked()
* @see PApplet#mouseMoved()
* @see PApplet#mouseDragged()
* @see PApplet#mouseButton
* @see PApplet#mouseWheel(MouseEvent)
*/
public boolean mousePressed;
// macOS: Ctrl + Left Mouse is converted to Right Mouse.
// This boolean tracks whether the conversion happened on PRESS,
// to report the same button during DRAG and on RELEASE,
// even though CTRL might have been released already.
// Otherwise, the events are inconsistent.
// https://github.com/processing/processing/issues/5672
private boolean macosCtrlClick;
/** @deprecated Use a mouse event handler that passes an event instead. */
@Deprecated
public MouseEvent mouseEvent;
/**
* The system variable
key always contains the value of the most
* recent key on the keyboard that was used (either pressed or released).
*
* For non-ASCII keys, use the
keyCode variable. The keys included
* in the ASCII specification (BACKSPACE, TAB, ENTER, RETURN, ESC, and
* DELETE) do not require checking to see if they key is coded, and you
* should simply use the
key variable instead of
keyCode If
* you're making cross-platform projects, note that the ENTER key is
* commonly used on PCs and Unix and the RETURN key is used instead on
* Macintosh. Check for both ENTER and RETURN to make sure your program
* will work for all platforms.
*
* There are issues with how
keyCode behaves across different
* renderers and operating systems. Watch out for unexpected behavior as
* you switch renderers and operating systems.
*
*
Advanced
*
* 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).
*
* @webref input:keyboard
* @webBrief The system variable that always contains the value of the most
* recent key on the keyboard that was used (either pressed or released)
* @see PApplet#keyCode
* @see PApplet#keyPressed
* @see PApplet#keyPressed()
* @see PApplet#keyReleased()
*/
public char key;
/**
* The variable keyCode is used to detect special keys such as the
* UP, DOWN, LEFT, RIGHT arrow keys and ALT, CONTROL, SHIFT.
*
* When checking for these keys, it can be useful to first check if the key
* is coded. This is done with the conditional if (key == CODED), as
* shown in the example above.
*
* The keys included in the ASCII specification (BACKSPACE, TAB, ENTER,
* RETURN, ESC, and DELETE) do not require checking to see if the key is
* coded; for those keys, you should simply use the key variable
* directly (and not keyCode). If you're making cross-platform
* projects, note that the ENTER key is commonly used on PCs and Unix,
* while the RETURN key is used on Macs. Make sure your program will work
* on all platforms by checking for both ENTER and RETURN.
*
* For those familiar with Java, the values for UP and DOWN are simply
* shorter versions of Java's KeyEvent.VK_UP and KeyEvent.VK_DOWN.
* Other keyCode values can be found in the Java
* KeyEvent
* reference.
*
* There are issues with how keyCode behaves across different
* renderers and operating systems. Watch out for unexpected behavior
* as you switch renderers and operating systems, and also whenever
* you are using keys not mentioned in this reference entry.
*
* If you are using P2D or P3D as your renderer, use the
* NEWT KeyEvent constants.
*
*
Advanced
* 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.
* ALT, CONTROL and SHIFT are also available. A full set of constants
* can be obtained from java.awt.event.KeyEvent, from the VK_XXXX variables.
*
* @webref input:keyboard
* @webBrief Used to detect special keys such as the UP, DOWN, LEFT, RIGHT arrow keys and ALT, CONTROL, SHIFT
* @see PApplet#key
* @see PApplet#keyPressed
* @see PApplet#keyPressed()
* @see PApplet#keyReleased()
*/
public int keyCode;
/**
* The boolean system variable keyPressed is true
* if any key is pressed and false if no keys are pressed.
*
* Note that there is a similarly named function called keyPressed().
* See its reference page for more information.
*
* @webref input:keyboard
* @webBrief The boolean system variable that is true if any key
* is pressed and false if no keys are pressed
* @see PApplet#key
* @see PApplet#keyCode
* @see PApplet#keyPressed()
* @see PApplet#keyReleased()
*/
public boolean keyPressed;
List pressedKeys = new ArrayList(6);
/**
* The last KeyEvent object passed into a mouse function.
* @deprecated Use a key event handler that passes an event instead.
*/
@Deprecated
public KeyEvent keyEvent;
/**
*
* Confirms if a Processing program is "focused", meaning that it is active
* and will accept input from mouse or keyboard. This variable is true if
* it is focused and false if not.
*
* @webref environment
* @webBrief Confirms if a Processing program is "focused"
*/
public boolean focused = false;
/**
* Time in milliseconds when the sketch was started.
*
* Used by the millis() function.
*/
long millisOffset = System.currentTimeMillis();
/**
*
* The system variable frameRate contains the approximate frame rate
* of the software as it executes. The initial value is 10 fps and is
* updated with each frame. The value is averaged (integrated) over several
* frames. As such, this value won't be valid until after 5-10 frames.
*
* @webref environment
* @webBrief The system variable that contains the approximate frame rate
* of the software as it executes
* @see PApplet#frameRate(float)
* @see PApplet#frameCount
*/
public float frameRate = 60;
protected boolean looping = true;
/** flag set to true when redraw() is called by the user */
protected boolean redraw = true;
/**
* The system variable frameCount contains the number o
* frames displayed since the program started. Inside setup()
* the value is 0 and during the first iteration of draw it is 1, etc.
*
* @webref environment
* @webBrief The system variable that contains the number of frames
* displayed since the program started
* @see PApplet#frameRate(float)
* @see PApplet#frameRate
*/
public int frameCount;
/** true if the sketch has stopped permanently. */
public volatile boolean finished;
/** used by the UncaughtExceptionHandler, so has to be static */
static Throwable uncaughtThrowable;
/**
* true if exit() has been called so that things shut down
* once the main thread kicks off.
*/
protected boolean exitCalled;
// ok to be static because it's not possible to mix enabled/disabled
static protected boolean disableAWT = System.getProperty("processing.awt.disable", "false").equals("true");;
// messages to send if attached as an external vm
/**
* Position of the upper left-hand corner of the editor window
* that launched this sketch.
*/
static public final String ARGS_EDITOR_LOCATION = "--editor-location";
static public final String ARGS_EXTERNAL = "--external";
/**
* Location for where to position the sketch window on screen.
*
* This is used by the editor to when saving the previous sketch
* location, or could be used by other classes to launch at a
* specific position on-screen.
*/
static public final String ARGS_LOCATION = "--location";
/** Used by the PDE to suggest a display (set in prefs, passed on Run) */
static public final String ARGS_DISPLAY = "--display";
/** Disable AWT so that LWJGL and others can run */
static public final String ARGS_DISABLE_AWT = "--disable-awt";
// static public final String ARGS_SPAN_DISPLAYS = "--span";
static public final String ARGS_BGCOLOR = "--bgcolor";
static public final String ARGS_FULL_SCREEN = "--full-screen";
static public final String ARGS_WINDOW_COLOR = "--window-color";
static public final String ARGS_PRESENT = "--present";
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";
static public final String ARGS_UI_SCALE = "--ui-scale";
/**
* When run externally to a PdeEditor,
* this is sent by the sketch when it quits.
*/
static public final String EXTERNAL_STOP = "__STOP__";
/**
* When run externally to a PDE Editor, this is sent by the sketch
* 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.";
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
protected PSurface surface;
public PSurface getSurface() {
return surface;
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
boolean insideSettings;
String renderer = JAVA2D;
int smooth = 1; // default smoothing (whatever that means for the renderer)
boolean fullScreen;
int display = -1; // use default
// Unlike the others above, needs to be public to support
// the pixelWidth and pixelHeight fields.
public int pixelDensity = 1;
boolean pixelDensityWarning = false;
boolean present;
String outputPath;
OutputStream outputStream;
// Background default needs to be different from the default value in
// PGraphics.backgroundColor, otherwise sketches that have size(100, 100)
// appear to be larger than they are, because the bg color matches.
// https://github.com/processing/processing/issues/2297
int windowColor = 0xffDDDDDD;
/**
* @param method "size" or "fullScreen"
* @param args parameters passed to the function to show the user
* @return true if safely inside the settings() method
*/
boolean insideSettings(String method, Object... args) {
if (insideSettings) {
return true;
}
final String url = "https://processing.org/reference/" + method + "_.html";
if (!external) { // post a warning for users of Eclipse and other IDEs
StringList argList = new StringList(args);
System.err.println("When not using the PDE, " + method + "() can only be used inside settings().");
System.err.println("Remove the " + method + "() method from setup(), and add the following:");
System.err.println("public void settings() {");
System.err.println(" " + method + "(" + argList.join(", ") + ");");
System.err.println("}");
}
throw new IllegalStateException(method + "() cannot be used here, see " + url);
}
void handleSettings() {
insideSettings = true;
if (!disableAWT) {
displayWidth = ShimAWT.getDisplayWidth();
displayHeight = ShimAWT.getDisplayHeight();
} else {
// https://github.com/processing/processing4/issues/57
System.err.println("AWT disabled, displayWidth/displayHeight will be 0");
}
// Here's where size(), fullScreen(), smooth(N) and noSmooth() might
// be called, conjuring up the demons of various rendering configurations.
settings();
if (display == SPAN && platform == MACOS) {
// Make sure "Displays have separate Spaces" is unchecked
// in System Preferences > Mission Control
Process p = exec("defaults", "read", "com.apple.spaces", "spans-displays");
BufferedReader outReader = createReader(p.getInputStream());
BufferedReader errReader = createReader(p.getErrorStream());
StringBuilder stdout = new StringBuilder();
StringBuilder stderr = new StringBuilder();
String line;
try {
while ((line = outReader.readLine()) != null) {
stdout.append(line);
}
while ((line = errReader.readLine()) != null) {
stderr.append(line);
}
} catch (IOException e) {
printStackTrace(e);
}
int resultCode = -1;
try {
resultCode = p.waitFor();
} catch (InterruptedException ignored) { }
if (resultCode == 1) {
String msg = trim(stderr.toString());
// This message is confusing, so don't print if it's something typical
if (!(msg.contains("The domain/default pair") && msg.contains("does not exist"))) {
System.err.println("Could not check the status of “Displays have separate spaces.”");
System.err.println("Result for 'defaults read' was " + resultCode);
System.err.println(msg);
}
}
String processOutput = trim(stdout.toString());
// On Catalina, the option may not be set, so resultCode
// will be 1 (an error, since the param doesn't exist.)
// But "Displays have separate spaces" is on by default.
// For Monterey, it appears to not be set until the user
// has visited the Mission Control preference pane once.
if (resultCode == 1 || "0".equals(processOutput)) {
System.err.println("To use fullScreen(SPAN), visit System Preferences → Mission Control");
System.err.println("and make sure that “Displays have separate spaces” is turned off.");
System.err.println("Then log out and log back in.");
}
}
insideSettings = false;
}
/**
* The settings() function is new with Processing 3.0.
* It's not needed in most sketches. It's only useful when it's
* absolutely necessary to define the parameters to size()
* with a variable. Alternately, the settings() function
* is necessary when using Processing code outside the
* Processing Development Environment (PDE). For example, when
* using the Eclipse code editor, it's necessary to use
* settings() to define the size() and
* smooth() values for a sketch.
*
* The settings() method runs before the sketch has been
* set up, so other Processing functions cannot be used at that
* point. For instance, do not use loadImage() inside settings().
* The settings() method runs "passively" to set a few variables,
* compared to the setup() command that call commands in
* the Processing API.
*
* @webref environment
* @webBrief Used when absolutely necessary to define the parameters to size()
* with a variable
* @see PApplet#fullScreen()
* @see PApplet#setup()
* @see PApplet#size(int,int)
* @see PApplet#smooth()
*/
public void settings() {
// is this necessary? (doesn't appear to be, so removing)
//size(DEFAULT_WIDTH, DEFAULT_HEIGHT, JAVA2D);
}
final public int sketchWidth() {
return width;
}
final public int sketchHeight() {
return height;
}
final public String sketchRenderer() {
return renderer;
}
// smoothing 1 is default.. 0 is none.. 2,4,8 depend on renderer
final public int sketchSmooth() {
return smooth;
}
final public boolean sketchFullScreen() {
return fullScreen;
}
// Numbered from 1, SPAN (0) means all displays, -1 means the default display
final public int sketchDisplay() {
return display;
}
final public String sketchOutputPath() {
return outputPath;
}
final public OutputStream sketchOutputStream() {
return outputStream;
}
final public int sketchWindowColor() {
return windowColor;
}
final public int sketchPixelDensity() {
return pixelDensity;
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
*
* This function returns the number "2" if the screen is a high-density
* screen (called a Retina display on OS X or high-dpi on Windows and Linux)
* and a "1" if not. This information is useful for a program to adapt to
* run at double the pixel density on a screen that supports it.
*
* @webref environment
* @webBrief Returns "2" if the screen is high-density and "1" if not
* @see PApplet#pixelDensity(int)
* @see PApplet#size(int,int)
*/
public int displayDensity() {
if (display != SPAN && (fullScreen || present)) {
return displayDensity(display);
}
int displayCount = 0;
if (!disableAWT) {
displayCount = ShimAWT.getDisplayCount();
} else {
// https://github.com/processing/processing4/issues/57
System.err.println("display count needs to be implemented for non-AWT");
}
// walk through all displays, use 2 if any display is 2
for (int i = 0; i < displayCount; i++) {
if (displayDensity(i+1) == 2) {
return 2;
}
}
// If nobody's density is 2 then everyone is 1
return 1;
}
/**
* @param display the display number to check
* (1-indexed to match the Preferences dialog box)
*/
public int displayDensity(int display) {
if (!disableAWT) {
return ShimAWT.getDisplayDensity(display);
}
/*
if (display > 0 && display registerWithArgs("mouseEvent", target, new Class[] { MouseEvent.class });
case "keyEvent" -> registerWithArgs("keyEvent", target, new Class[] { KeyEvent.class });
case "touchEvent" -> registerWithArgs("touchEvent", target, new Class[] { TouchEvent.class });
default -> registerNoArgs(methodName, target);
}
}
private void registerNoArgs(String name, Object o) {
Class c = o.getClass();
try {
Method method = c.getMethod(name);
RegisteredMethods meth = registerMap.get(name);
if (meth == null) {
meth = new RegisteredMethods();
registerMap.put(name, meth);
}
meth.add(o, method);
} catch (NoSuchMethodException nsme) {
die("There is no public " + name + "() method in the class " +
o.getClass().getName());
} catch (Exception e) {
die("Could not register " + name + " + () for " + o, e);
}
}
private void registerWithArgs(String name, Object o, Class[] cargs) {
Class c = o.getClass();
try {
Method method = c.getMethod(name, cargs);
RegisteredMethods meth = registerMap.get(name);
if (meth == null) {
meth = new RegisteredMethods();
registerMap.put(name, meth);
}
meth.add(o, method);
} catch (NoSuchMethodException nsme) {
die("There is no public " + name + "() method in the class " +
o.getClass().getName());
} catch (Exception e) {
die("Could not register " + name + " + () for " + o, e);
}
}
public void unregisterMethod(String name, Object target) {
RegisteredMethods meth = registerMap.get(name);
if (meth == null) {
die("No registered methods with the name " + name + "() were found.");
} else {
try {
meth.remove(target);
} catch (Exception e) {
die("Could not unregister " + name + "() for " + target, e);
}
}
}
protected void handleMethods(String methodName, Object...args) {
RegisteredMethods meth = registerMap.get(methodName);
if (meth != null) {
meth.handle(args);
}
}
//////////////////////////////////////////////////////////////
/**
*
* The setup() function is run once, when the program starts. It's used
* to define initial environment properties such as screen size and to load media
* such as images and fonts as the program starts. There can only be one
* setup() function for each program, and it shouldn't be called again
* after its initial execution.
*
* If the sketch is a different dimension than the default, the size()
* function or fullScreen() function must be the first line in
* setup().
*
* Note: Variables declared within setup() are not accessible within
* other functions, including draw().
*
* @webref structure
* @webBrief The setup() function is called once when the program starts
* @usage web_application
* @see PApplet#size(int, int)
* @see PApplet#loop()
* @see PApplet#noLoop()
* @see PApplet#draw()
*/
public void setup() {
}
/**
*
* Called directly after setup(), the draw() function continuously
* executes the lines of code contained inside its block until the program is
* stopped or noLoop() is called. draw() is called automatically
* and should never be called explicitly. All Processing programs update the
* screen at the end of draw(), never earlier.
*
* To stop the code inside of draw() from running continuously, use
* noLoop(), redraw() and loop(). If noLoop() is
* used to stop the code in draw() from running, then redraw()
* will cause the code inside draw() to run a single time, and
* loop() will cause the code inside draw() to resume running
* continuously.
*
* The number of times draw() executes in each second may be controlled
* with the frameRate() function.
*
* It is common to call background() near the beginning of the
* draw() loop to clear the contents of the window, as shown in the first
* example above. Since pixels drawn to the window are cumulative, omitting
* background() may result in unintended results.
*
* There can only be one draw() function for each sketch, and draw()
* must exist if you want the code to run continuously, or to process events such
* as mousePressed(). Sometimes, you might have an empty call to
* draw() in your program, as shown in the second example above.
*
* @webref structure
* @webBrief Called directly after setup() and continuously executes the lines
* of code contained inside its block until the program is stopped or
* noLoop() is called
* @usage web_application
* @see PApplet#setup()
* @see PApplet#loop()
* @see PApplet#noLoop()
* @see PApplet#redraw()
* @see PApplet#frameRate(float)
* @see PGraphics#background(float, float, float, float)
*/
public void draw() {
// if no draw method, then shut things down
//System.out.println("no draw method, goodbye");
finished = true;
}
//////////////////////////////////////////////////////////////
/*
protected void resizeRenderer(int newWidth, int newHeight) {
debug("resizeRenderer request for " + newWidth + " " + newHeight);
if (width != newWidth || height != newHeight) {
debug(" former size was " + width + " " + height);
g.setSize(newWidth, newHeight);
width = newWidth;
height = newHeight;
}
}
*/
/**
* Create a full-screen sketch using the default renderer.
*/
public void fullScreen() {
if (!fullScreen) {
if (insideSettings("fullScreen")) {
this.fullScreen = true;
}
}
}
public void fullScreen(int display) {
if (!fullScreen || display != this.display) {
if (insideSettings("fullScreen", display)) {
this.fullScreen = true;
this.display = display;
}
}
}
/**
* This function is new for Processing 3.0. It opens a sketch using the full
* size of the computer's display. This function must be the first line in
* setup(). The size() and fullScreen() functions cannot
* both be used in the same program, just choose one.
*
* When fullScreen() is used without a parameter, it draws the sketch
* to the screen currently selected inside the Preferences window. When it is
* used with a single parameter, this number defines the screen to display to
* program on (e.g. 1, 2, 3...). When used with two parameters, the first
* defines the renderer to use (e.g. P2D) and the second defines the screen.
* The SPAN parameter can be used in place of a screen number to draw
* the sketch as a full-screen window across all the attached displays if
* there are more than one.
*
* Prior to Processing 3.0, a full-screen program was defined with
* size(displayWidth, displayHeight).
*
* @webref environment
* @webBrief Opens a sketch using the full size of the computer's display
* @param renderer the renderer to use, e.g. P2D, P3D, JAVA2D (default)
* @see PApplet#settings()
* @see PApplet#setup()
* @see PApplet#size(int,int)
* @see PApplet#smooth()
*/
public void fullScreen(String renderer) {
if (!fullScreen ||
!renderer.equals(this.renderer)) {
if (insideSettings("fullScreen", renderer)) {
this.fullScreen = true;
this.renderer = renderer;
}
}
}
/**
* @param display the screen to run the sketch on (1, 2, 3, etc. or on multiple screens using SPAN)
*/
public void fullScreen(String renderer, int display) {
if (!fullScreen ||
!renderer.equals(this.renderer) ||
display != this.display) {
if (insideSettings("fullScreen", renderer, display)) {
this.fullScreen = true;
this.renderer = renderer;
this.display = display;
}
}
}
/**
* Defines the dimension of the display window width and height in units of
* pixels. In a program that has the setup() function, the
* size() function must be the first line of code inside
* setup(), and the setup() function must appear in the code tab
* with the same name as your sketch folder.
*
* The built-in variables width and height are set by the
* parameters passed to this function. For example, running size(640,
* 480) will assign 640 to the width variable and 480 to the height
* variable. If size() is not used, the window will be given a
* default size of 100 x 100 pixels.
*
* The size() function can only be used once inside a sketch, and it
* cannot be used for resizing. Use windowResize() instead.
*
* To run a sketch that fills the screen, use the fullScreen() function,
* rather than using size(displayWidth, displayHeight).
*
* The renderer parameter selects which rendering engine to use. For
* example, if you will be drawing 3D shapes, use P3D. The default
* renderer is slower for some situations (for instance large or
* high-resolution displays) but generally has higher quality than the
* other renderers for 2D drawing.
*
* In addition to the default renderer, other renderers are:
*
* P2D (Processing 2D): 2D graphics renderer that makes use of
* OpenGL-compatible graphics hardware.
*
* P3D (Processing 3D): 3D graphics renderer that makes use of
* OpenGL-compatible graphics hardware.
*
* FX2D (JavaFX 2D): A 2D renderer that uses JavaFX, which may be
* faster for some applications, but has some compatibility quirks.
* Use “Manage Libraries” to download and install the JavaFX library.
*
* PDF: The PDF renderer draws 2D graphics directly to an Acrobat PDF
* file. This produces excellent results when you need vector shapes for
* high-resolution output or printing. You must first use Import Library
* → PDF to make use of the library. More information can be found in the
* PDF library reference.
*
* SVG: The SVG renderer draws 2D graphics directly to an SVG file.
* This is great for importing into other vector programs or using for
* digital fabrication. It is not as feature-complete as other renderers.
* Like PDF, you must first use Import Library → SVG Export to
* make use the SVG library.
*
* As of Processing 3.0, to use variables as the parameters to size()
* function, place the size() function within the settings()
* function (instead of setup()). There is more information about this
* on the settings() reference page.
*
* The maximum width and height is limited by your operating system, and is
* usually the width and height of your actual screen. On some machines it may
* simply be the number of pixels on your current screen, meaning that a
* screen of 800 x 600 could support size(1600, 300), since that is the
* same number of pixels. This varies widely, so you'll have to try different
* rendering modes and sizes until you get what you're looking for. If you
* need something larger, use createGraphics to create a non-visible
* drawing surface.
*
* The minimum width and height is around 100 pixels in each direction. This
* is the smallest that is supported across Windows, macOS, and Linux. We
* enforce the minimum size so that sketches will run identically on different
* machines.
*
*
* @webref environment
* @webBrief Defines the dimension of the display window in units of pixels
* @param width
* width of the display window in units of pixels
* @param height
* height of the display window in units of pixels
* @see PApplet#width
* @see PApplet#height
* @see PApplet#setup()
* @see PApplet#settings()
* @see PApplet#fullScreen()
*/
public void size(int width, int height) {
// Check to make sure the width/height have actually changed. It's ok to
// have size() duplicated (and may be better to not remove it from where
// it sits in the code anyway when adding it to settings()). Only take
// action if things have changed.
if (width != this.width ||
height != this.height) {
if (insideSettings("size", width, height)) {
this.width = width;
this.height = height;
}
}
}
public void size(int width, int height, String renderer) {
if (width != this.width ||
height != this.height ||
!renderer.equals(this.renderer)) {
//println(width, height, renderer, this.width, this.height, this.renderer);
if (insideSettings("size", width, height, "\"" + renderer + "\"")) {
this.width = width;
this.height = height;
this.renderer = renderer;
}
}
}
/**
* @nowebref
*/
public void size(int width, int height, String renderer, String path) {
// Don't bother checking path, it's probably been modified to absolute,
// so it would always trigger. But the alternative is comparing the
// canonical file, which seems overboard.
if (width != this.width ||
height != this.height ||
!renderer.equals(this.renderer)) {
if (insideSettings("size", width, height, "\"" + renderer + "\"",
"\"" + path + "\"")) {
this.width = width;
this.height = height;
this.renderer = renderer;
this.outputPath = path;
}
}
}
public PGraphics createGraphics(int w, int h) {
return createGraphics(w, h, JAVA2D);
}
/**
*
* Creates and returns a new PGraphics object. Use this class if you
* need to draw into an offscreen graphics buffer. The first two parameters
* define the width and height in pixels. The third, optional parameter
* specifies the renderer. It can be defined as P2D, P3D, PDF, or SVG. If the
* third parameter isn't used, the default renderer is set. The PDF and SVG
* renderers require the filename parameter.
*
* It's important to consider the renderer used with createGraphics()
* in relation to the main renderer specified in size(). For example,
* it's only possible to use P2D or P3D with createGraphics() when one
* of them is defined in size(). Unlike Processing 1.0, P2D and P3D use
* OpenGL for drawing, and when using an OpenGL renderer it's necessary for
* the main drawing surface to be OpenGL-based. If P2D or P3D are used as the
* renderer in size(), then any of the options can be used with
* createGraphics(). If the default renderer is used in size(),
* then only the default, PDF, or SVG can be used with
* createGraphics().
*
* It's important to run all drawing functions between the beginDraw()
* and endDraw(). As the exception to this rule, smooth() should
* be run on the PGraphics object before beginDraw(). See the reference
* for smooth() for more detail.
*
* The createGraphics() function should almost never be used inside
* draw() because of the memory and time needed to set up the graphics.
* One-time or occasional use during draw() might be acceptable, but
* code that calls createGraphics() at 60 frames per second might run
* out of memory or freeze your sketch.
*
* Unlike the main drawing surface which is completely opaque, surfaces
* created with createGraphics() can have transparency. This makes it
* possible to draw into a graphics and maintain the alpha channel. By using
* save() to write a PNG or TGA file, the transparency of the graphics
* object will be honored.
*
*
Advanced
Create an offscreen PGraphics object for drawing. This
* can be used for bitmap or vector images drawing or rendering.
*
* - Do not use "new PGraphicsXxxx()", use this method. This method ensures
* that internal variables are set up properly that tie the new graphics
* context back to its parent PApplet.
*
- The basic way to create bitmap images is to use the
* saveFrame()
* function.
*
- If you want to create a really large scene and write that, first make
* sure that you've allocated a lot of memory in the Preferences.
*
- If you want to create images that are larger than the screen, you
* should create your own PGraphics object, draw to that, and use
* save().
*
*
*
* PGraphics big;
*
* void setup() {
* big = createGraphics(3000, 3000);
*
* 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");
* }
*
*
*
* - It's important to always wrap drawing to createGraphics() with
* beginDraw() and endDraw() (beginFrame() and endFrame() prior to revision
* 0115). The reason is that the renderer needs to know when drawing has
* stopped, so that it can update itself internally. This also handles calling
* the defaults() method, for people familiar with that.
*
- With Processing 0115 and later, it's possible to write images in
* formats other than the default .tga and .tiff. The exact formats and
* background information can be found in the developer's reference for
* PImage.save().
*
*
* @webref rendering
* @webBrief Creates and returns a new
PGraphics object of the types
* P2D or P3D
* @param w
* width in pixels
* @param h
* height in pixels
* @param renderer
* Either P2D, P3D, or PDF
* @see PGraphics#PGraphics
*
*/
public PGraphics createGraphics(int w, int h, String renderer) {
return createGraphics(w, h, renderer, null);
}
/**
* Create an offscreen graphics surface for drawing, in this case
* for a renderer that writes to a file (such as PDF or DXF).
* @param path the name of the file (can be an absolute or relative path)
*/
public PGraphics createGraphics(int w, int h,
String renderer, String path) {
return makeGraphics(w, h, renderer, path, false);
}
/**
* Version of createGraphics() used internally.
* @param path A path (or null if none), can be absolute or relative ({@link PApplet#savePath} will be called)
*/
protected PGraphics makeGraphics(int w, int h,
String renderer, String path,
boolean primary) {
if (!primary && !g.isGL()) {
if (renderer.equals(P2D)) {
throw new RuntimeException("createGraphics() with P2D requires size() to use P2D or P3D");
} else if (renderer.equals(P3D)) {
throw new RuntimeException("createGraphics() with P3D or OPENGL requires size() to use P2D or P3D");
}
}
try {
Class rendererClass =
Thread.currentThread().getContextClassLoader().loadClass(renderer);
Constructor constructor = rendererClass.getConstructor();
PGraphics pg = (PGraphics) constructor.newInstance();
pg.setParent(this);
pg.setPrimary(primary);
if (path != null) {
pg.setPath(savePath(path));
}
// pg.setQuality(sketchQuality());
// if (!primary) {
// surface.initImage(pg, w, h);
// }
pg.setSize(w, h);
// everything worked, return it
return pg;
} catch (InvocationTargetException ite) {
String msg = ite.getTargetException().getMessage();
if ((msg != null) &&
(msg.contains("no jogl in java.library.path"))) {
// Is this true anymore, since the JARs contain the native libs?
throw new RuntimeException("The jogl library folder needs to be " +
"specified with -Djava.library.path=/path/to/jogl");
} else {
printStackTrace(ite.getTargetException());
Throwable target = ite.getTargetException();
/*
// removing for 3.2, we'll see
if (platform == MACOSX) {
target.printStackTrace(System.out); // OS X bug (still true?)
}
*/
throw new RuntimeException(target.getMessage());
}
} catch (ClassNotFoundException cnfe) {
// Clarify the error message for less confusion on 4.x
if (renderer.equals(FX2D)) {
renderer = "JavaFX";
}
if (external) {
throw new RuntimeException("Please use Sketch → Import Library " +
"to add " + renderer + " to your sketch.");
} else {
throw new RuntimeException("The " + renderer +
" renderer is not in the class path.");
}
} catch (Exception e) {
if ((e instanceof IllegalArgumentException) ||
(e instanceof NoSuchMethodException) ||
(e instanceof IllegalAccessException)) {
if (e.getMessage().contains("cannot be /dev/null 2>&1; fi;");
argList.append("if [ -f ~/.bash_profile ]; then . ~/.bash_profile >/dev/null 2>&1; elif [ -f ~/.bash_profile ]; then . ~/.bash_profile >/dev/null 2>&1; elif [ -f ~/.profile ]; then ~/.profile >/dev/null 2>&1; fi;");
}
for (String arg : args) {
argList.append(arg);
}
return exec(stdout, stderr, shell, runCmd, argList.join(" "));
}
/*
static private final String shellQuoted(String arg) {
if (arg.indexOf(' ') != -1) {
// check to see if already quoted
if ((arg.charAt(0) != '\"' || arg.charAt(arg.length()-1) != '\"') &&
(arg.charAt(0) != '\'' || arg.charAt(arg.length()-1) != '\'')) {
// see which quotes we can use
if (arg.indexOf('\"') == -1) {
// if no double quotes, try those first
return "\"" + arg + "\"";
} else if (arg.indexOf('\'') == -1) {
// if no single quotes, let's use those
return "'" + arg + "'";
}
}
}
return arg;
}
*/
//////////////////////////////////////////////////////////////
/**
* Better way of handling e.printStackTrace() calls so that they can be
* handled by subclasses as necessary.
*/
protected void printStackTrace(Throwable t) {
t.printStackTrace();
}
/**
* Function for an application to kill itself and display an error.
* Mostly this is here to be improved later.
*/
public void die(String what) {
dispose();
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);
}
/**
*
* Quits/stops/exits the program. Programs without a
draw() function
* exit automatically after the last line has run, but programs with
*
draw() run continuously until the program is manually stopped or
*
exit() is run.
*
* Rather than terminating immediately,
exit() will cause the sketch
* to exit after
draw() has completed (or after
setup()
* completes if called during the
setup() function).
*
* For Java programmers, this is
not the same as System.exit().
* Further, System.exit() should not be used because closing out an
* application while
draw() is running may cause a crash
* (particularly with P3D).
*
* @webref structure
* @webBrief Quits/stops/exits the program
*/
public void exit() {
if (surface.isStopped()) {
// exit immediately, dispose() has already been called,
// meaning that the main thread has long since exited
exitActual();
} else if (looping) {
// dispose() will be called as the thread exits
finished = true;
// tell the code to call exitActual() to do a System.exit()
// once the next draw() has completed
exitCalled = true;
} else { // !looping
// if not looping, shut down things explicitly,
// because the main thread will be sleeping
dispose();
// now get out
exitActual();
}
}
public boolean exitCalled() {
return exitCalled;
}
/**
* Some subclasses (I'm looking at you, processing.py) might wish to do something
* other than actually terminate the JVM. This gives them a chance to do whatever
* they have in mind when cleaning up.
*/
public void exitActual() {
System.exit(0);
}
/**
* Called to dispose of resources and shut down the sketch.
* Destroys the thread, dispose the renderer,and notify listeners.
*
* Not to be called or overridden by users. If called multiple times,
* will only notify listeners once. Register a "dispose" listener instead.
*/
public void dispose() {
// moved here from stop()
finished = true; // let the sketch know it is shut down time
// don't run the disposers twice
if (surface.stopThread()) {
// shut down renderer
if (g != null) {
g.dispose();
}
// run dispose() methods registered by libraries
handleMethods("dispose");
}
if (platform == MACOS) {
try {
final String td = "processing.core.ThinkDifferent";
final Class thinkDifferent = getClass().getClassLoader().loadClass(td);
thinkDifferent.getMethod("cleanup").invoke(null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//////////////////////////////////////////////////////////////
/**
* Call a method in the current class based on its name.
*
* Note that the function being called must be public. Inside the PDE,
* 'public' is automatically added, but when used without the preprocessor,
* (like from Eclipse) you'll have to do it yourself.
*/
public void method(String name) {
try {
Method method = getClass().getMethod(name);
method.invoke(this);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.getTargetException().printStackTrace();
} catch (NoSuchMethodException nsme) {
System.err.println("There is no public " + name + "() method " +
"in the class " + getClass().getName());
}
}
/**
* Processing sketches follow a specific sequence of steps:
setup()
* first, followed by
draw() over and over and over again in a loop. A
* thread is also a series of steps with a beginning, a middle, and an end. A
* Processing sketch is a single thread, often referred to as the "Animation"
* thread. Other threads' sequences, however, can run independently of the
* main animation loop. In fact, you can launch any number of threads at one
* time, and they will all run concurrently.
*
* You cannot draw to the screen from a function called by
thread().
* Because it runs independently, the code will not be synchronized to the
* animation thread, causing strange or at least inconsistent results. Use
*
thread() to load files or do other tasks that take time. When the
* task is finished, set a variable that indicates the task is complete, and
* check that from inside your
draw() method.
*
* Processing uses threads quite often, such as with library functions like
*
captureEvent() and
movieEvent(). These functions are
* triggered by a different thread running behind the scenes, and they alert
* Processing whenever they have something to report. This is useful when you
* need to perform a task that takes too long and would slow down the main
* animation's frame rate, such as grabbing data from the network. If a
* separate thread gets stuck or has an error, the entire program won't grind
* to a halt, since the error only stops that individual thread.
*
* Writing your own thread can be a complex endeavor that involves extending
* the Java
Thread
* class. However, the
thread() method is a quick and dirty way to
* implement a simple thread in Processing. By passing in a
String that
* matches the name of a function declared elsewhere in the sketch, Processing
* will execute that function in a separate thread.
*
* @webref structure
* @webBrief Launch a new thread and call the specified function from that new
* thread
* @usage Application
* @param name
* name of the function to be executed in a separate thread
* @see PApplet#setup()
* @see PApplet#draw()
* @see PApplet#loop()
* @see PApplet#noLoop()
*/
public void thread(final String name) {
new Thread(() -> method(name)).start();
}
//////////////////////////////////////////////////////////////
// SCREEN GRABASS
/**
*
* Saves an image from the display window. Append a file extension to the name
* of the file, to indicate the file format to be used: either TIFF (.tif),
* TARGA (.tga), JPEG (.jpg), or PNG (.png). If no extension is included in
* the filename, the image will save in TIFF format and
.tif will be
* added to the name. These files are saved to the sketch's folder, which may
* be opened by selecting "Show sketch folder" from the "Sketch" menu.
* Alternatively, the files can be saved to any location on the computer by
* using an absolute path (something that starts with / on Unix and Linux, or
* a drive letter on Windows).
*
* All images saved from the main drawing window will be opaque. To save
* images without a background, use
createGraphics().
*
* @webref output:image
* @webBrief Saves an image from the display window
* @param filename
* any sequence of letters and numbers
* @see PApplet#saveFrame()
* @see PApplet#createGraphics(int, int, String)
*/
public void save(String filename) {
g.save(savePath(filename));
}
/**
*/
public void saveFrame() {
g.save(savePath("screen-" + nf(frameCount, 4) + ".tif"));
}
/**
*
* Saves a numbered sequence of images, one image each time the function is
* run. To save an image that is identical to the display window, run the
* function at the end of
draw() or within mouse and key events such as
*
mousePressed() and
keyPressed(). Use the Movie Maker program
* in the Tools menu to combine these images to a movie.
*
* If
saveFrame() is used without parameters, it will save files as
* screen-0000.tif, screen-0001.tif, and so on. You can specify the name of
* the sequence with the
filename parameter, including hash marks
* (####), which will be replaced by the current
frameCount value. (The
* number of hash marks is used to determine how many digits to include in the
* file names.) Append a file extension, to indicate the file format to be
* used: either TIFF (.tif), TARGA (.tga), JPEG (.jpg), or PNG (.png). Image
* files are saved to the sketch's folder, which may be opened by selecting
* "Show Sketch Folder" from the "Sketch" menu.
*
* Alternatively, the files can be saved to any location on the computer by
* using an absolute path (something that starts with / on Unix and Linux, or
* a drive letter on Windows).
*
* All images saved from the main drawing window will be opaque. To save
* images without a background, use
createGraphics().
*
* @webref output:image
* @webBrief Saves a numbered sequence of images, one image each time the
* function is run
* @see PApplet#save(String)
* @see PApplet#createGraphics(int, int, String, String)
* @see PApplet#frameCount
* @param filename
* any sequence of letters or numbers that ends with either ".tif",
* ".tga", ".jpg", or ".png"
*/
public void saveFrame(String filename) {
g.save(savePath(insertFrame(filename)));
}
/**
* 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.
*/
public 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
//
/**
* Set the cursor type
* @param kind either ARROW, CROSS, HAND, MOVE, TEXT, or WAIT
*/
public void cursor(int kind) {
surface.setCursor(kind);
}
/**
* 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 img) {
cursor(img, img.width/2, img.height/2);
}
/**
*
* Sets the cursor to a predefined symbol or an image, or makes it visible if
* already hidden. If you are trying to set an image as the cursor, the
* recommended size is 16x16 or 32x32 pixels. The values for parameters
*
x and
y must be less than the dimensions of the image.
*
* Setting or hiding the cursor does not generally work with "Present" mode
* (when running full-screen).
*
* With the P2D and P3D renderers, a generic set of cursors are used because
* the OpenGL renderer doesn't have access to the default cursor images for
* each platform
* (
Issue
* 3791).
*
* @webref environment
* @webBrief Sets the cursor to a predefined symbol, an image, or makes it
* visible if already hidden
* @see PApplet#noCursor()
* @param img
* any variable of type PImage
* @param x
* the horizontal active spot of the cursor
* @param y
* the vertical active spot of the cursor
*/
public void cursor(PImage img, int x, int y) {
surface.setCursor(img, x, y);
}
/**
* Show the cursor after noCursor() was called.
* Notice that the program remembers the last set cursor type
*/
public void cursor() {
surface.showCursor();
}
/**
*
* Hides the mouse cursor from view.
*
*
Advanced
* Hide the cursor by creating a transparent image
* and using it as a custom cursor.
* @webref environment
* @webBrief Hides the cursor from view
* @see PApplet#cursor()
* @usage Application
*/
public void noCursor() {
surface.hideCursor();
}
//////////////////////////////////////////////////////////////
/**
*
* The
print() function writes to the console area, the black rectangle
* at the bottom of the Processing environment. This function is often helpful
* for looking at the data a program is producing. The companion function
*
println() works like
print(), but creates a new line of text
* for each call to the function. More than one parameter can be passed into the
* function by separating them with commas. Alternatively, individual elements
* can be separated with quotes ("") and joined with the addition operator
* (+).
*
* Using
print() on an object will output
null, a memory location
* that may look like "@10be08," or the result of the
toString() method
* from the object that's being printed. Advanced users who want more useful
* output when calling
print() on their own classes can add a
*
toString() method to the class that returns a String.
*
* Note that the console is relatively slow. It works well for occasional
* messages, but does not support high-speed, real-time output (such as at 60
* frames per second). It should also be noted, that a print() within a for loop
* can sometimes lock up the program, and cause the sketch to freeze.
*
* @webref output:text area
* @webBrief Writes to the console area of the Processing environment
* @usage IDE
* @param what
* data to print to console
* @see PApplet#println()
* @see PApplet#printArray(Object)
* @see PApplet#join(String[], char)
*/
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(long 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(double what) {
System.out.print(what);
System.out.flush();
}
static public void print(String what) {
System.out.print(what);
System.out.flush();
}
/**
* @param variables list of data, separated by commas
*/
static public void print(Object... variables) {
StringBuilder sb = new StringBuilder();
for (Object o : variables) {
if (sb.length() != 0) {
sb.append(" ");
}
if (o == null) {
sb.append("null");
} else {
sb.append(o);
}
}
System.out.print(sb);
}
/**
*
* The
println() function writes to the console area, the black
* rectangle at the bottom of the Processing environment. This function is
* often helpful for looking at the data a program is producing. Each call to
* this function creates a new line of output. More than one parameter can be
* passed into the function by separating them with commas. Alternatively,
* individual elements can be separated with quotes ("") and joined with the
* addition operator (+).
*
* Before Processing 2.1,
println() was used to write array data to the
* console. Now, use
printArray() to write array data to the
* console.
*
* Note that the console is relatively slow. It works well for occasional
* messages, but does not support high-speed, real-time output (such as at 60
* frames per second). It should also be noted, that a println() within a for
* loop can sometimes lock up the program, and cause the sketch to freeze.
*
* @webref output:text area
* @webBrief Writes to the text area of the Processing environment's console
* @usage IDE
* @see PApplet#print(byte)
* @see PApplet#printArray(Object)
*/
static public void println() {
System.out.println();
}
/**
* @param what data to print to console
*/
static public void println(byte what) {
System.out.println(what);
System.out.flush();
}
static public void println(boolean what) {
System.out.println(what);
System.out.flush();
}
static public void println(char what) {
System.out.println(what);
System.out.flush();
}
static public void println(int what) {
System.out.println(what);
System.out.flush();
}
static public void println(long what) {
System.out.println(what);
System.out.flush();
}
static public void println(float what) {
System.out.println(what);
System.out.flush();
}
static public void println(double what) {
System.out.println(what);
System.out.flush();
}
static public void println(String what) {
System.out.println(what);
System.out.flush();
}
/**
* @param variables list of data, separated by commas
*/
static public void println(Object... variables) {
// System.out.println("got " + variables.length + " variables");
print(variables);
println();
}
/*
// Breaking this out since the compiler doesn't know the difference between
// Object... and just Object (with an array passed in). This should take care
// of the confusion for at least the most common case (a String array).
// On second thought, we're going the printArray() route, since the other
// object types are also used frequently.
static public void println(String[] array) {
for (int i = 0; i < array.length; i++) {
System.out.println("[" + i + "] \"" + array[i] + "\"");
}
System.out.flush();
}
*/
/**
* For arrays, use printArray() instead. This function causes a warning
* because the new print(Object...) and println(Object...) functions can't
* be reliably bound by the compiler.
*/
static public void println(Object what) {
if (what == null) {
System.out.println("null");
} else if (what.getClass().isArray()) {
printArray(what);
} else {
System.out.println(what);
System.out.flush();
}
}
/**
*
* The
printArray() function writes array data to the text
* area of the Processing environment's console. A new line
* is put between each element of the array. This function
* can only print one dimensional arrays.
* Note that the console is relatively slow. It works well
* for occasional messages, but does not support high-speed,
* real-time output (such as at 60 frames per second).
*
* @webref output:text area
* @webBrief Writes array data to the text
* area of the Processing environment's console.
* @param what one-dimensional array
* @usage IDE
* @see PApplet#print(byte)
* @see PApplet#println()
*/
static public void printArray(Object what) {
if (what == null) {
// special case since this does fugly 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 multidimensional arrays (case '[')
// or anything else that's not int, float, boolean, char
System.out.println(what);
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]);
}
}
}
case 'Z' -> { // boolean
boolean[] zz = (boolean[]) what;
for (int i = 0; i < zz.length; i++) {
System.out.println("[" + i + "] " + zz[i]);
}
}
case 'B' -> { // byte
byte[] bb = (byte[]) what;
for (int i = 0; i < bb.length; i++) {
System.out.println("[" + i + "] " + bb[i]);
}
}
case 'C' -> { // char
char[] cc = (char[]) what;
for (int i = 0; i < cc.length; i++) {
System.out.println("[" + i + "] '" + cc[i] + "'");
}
}
case 'I' -> { // int
int[] ii = (int[]) what;
for (int i = 0; i < ii.length; i++) {
System.out.println("[" + i + "] " + ii[i]);
}
}
case 'J' -> { // int
long[] jj = (long[]) what;
for (int i = 0; i < jj.length; i++) {
System.out.println("[" + i + "] " + jj[i]);
}
}
case 'F' -> { // float
float[] ff = (float[]) what;
for (int i = 0; i < ff.length; i++) {
System.out.println("[" + i + "] " + ff[i]);
}
}
case 'D' -> { // double
double[] dd = (double[]) what;
for (int i = 0; i < dd.length; i++) {
System.out.println("[" + i + "] " + dd[i]);
}
}
default -> System.out.println(what);
}
} else { // not an array
System.out.println(what);
}
}
System.out.flush();
}
static public void debug(String msg) {
if (DEBUG) println(msg);
}
//
/*
// 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 sketches, and casting
// things all the time is annoying, thus the functions below.
/**
*
* Calculates the absolute value (magnitude) of a number. The absolute
* value of a number is always positive.
*
* @webref math:calculation
* @webBrief Calculates the absolute value (magnitude) of a number
* @param n number to compute
*/
static public final float abs(float n) {
return (n < 0) ? -n : n;
}
static public final int abs(int n) {
return (n < 0) ? -n : n;
}
/**
*
* Squares a number (multiplies a number by itself). The result is always a
* positive number, as multiplying two negative numbers always yields a
* positive result. For example,
-1 * -1 = 1.
*
* @webref math:calculation
* @webBrief Squares a number (multiplies a number by itself)
* @param n number to square
* @see PApplet#sqrt(float)
*/
static public final float sq(float n) {
return n*n;
}
/**
*
* Calculates the square root of a number. The square root of a number is
* always positive, even though there may be a valid negative root. The
* square root
s of number
a is such that
s*s = a. It
* is the opposite of squaring.
*
* @webref math:calculation
* @webBrief Calculates the square root of a number
* @param n non-negative number
* @see PApplet#pow(float, float)
* @see PApplet#sq(float)
*/
static public final float sqrt(float n) {
return (float)Math.sqrt(n);
}
/**
*
* Calculates the natural logarithm (the base-
e logarithm) of a
* number. This function expects the values greater than 0.0.
*
* @webref math:calculation
* @webBrief Calculates the natural logarithm (the base-
e logarithm) of a
* number
* @param n number greater than 0.0
*/
static public final float log(float n) {
return (float)Math.log(n);
}
/**
*
* Returns Euler's number
e (2.71828...) raised to the power of the
*
value parameter.
*
* @webref math:calculation
* @webBrief Returns Euler's number
e (2.71828...) raised to the power of the
*
value parameter
* @param n exponent to raise
*/
static public final float exp(float n) {
return (float)Math.exp(n);
}
/**
*
* Facilitates exponential expressions. The
pow() function is an
* efficient way of multiplying numbers by themselves (or their reciprocal)
* in large quantities. For example,
pow(3, 5) is equivalent to the
* expression 3*3*3*3*3 and
pow(3, -5) is equivalent to 1 / 3*3*3*3*3.
*
* @webref math:calculation
* @webBrief Facilitates exponential expressions
* @param n base of the exponential expression
* @param e power by which to raise the base
* @see PApplet#sqrt(float)
*/
static public final float pow(float n, float e) {
return (float)Math.pow(n, e);
}
/**
*
* Determines the largest value in a sequence of numbers, and then returns that
* value.
max() accepts either two or three
float or
int
* values as parameters, or an array of any length.
*
* @webref math:calculation
* @webBrief Determines the largest value in a sequence of numbers
* @param a
* first number to compare
* @param b
* second number to compare
* @see PApplet#min(float, float, float)
*/
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 double max(double a, double b) {
return (a > b) ? a : b;
}
*/
/**
* @param c third number to compare
*/
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);
}
/**
* @param list array of numbers to compare
*/
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;
}
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;
}
// /**
// * 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;
}
static public final float min(float a, float b) {
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);
}
/**
*
* Determines the smallest value in a sequence of numbers, and then returns that
* value.
min() accepts either two or three
float or
int
* values as parameters, or an array of any length.
*
* @webref math:calculation
* @webBrief Determines the smallest value in a sequence of numbers
* @param a
* first number
* @param b
* second number
* @param c
* third number
* @see PApplet#max(float, float, float)
*/
static public final float min(float a, float b, float c) {
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);
}
*/
/**
* @param list array of numbers to compare
*/
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;
}
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;
}
/*
* 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);
}
/**
*
* Constrains a value to not exceed a maximum and minimum value.
*
* @webref math:calculation
* @webBrief Constrains a value to not exceed a maximum and minimum value
* @param amt the value to constrain
* @param low minimum limit
* @param high maximum limit
* @see PApplet#max(float, float, float)
* @see PApplet#min(float, float, float)
*/
static public final float constrain(float amt, float low, float high) {
return (amt < low) ? low : ((amt > high) ? high : amt);
}
/**
*
* Calculates the sine of an angle. This function expects the values of the
*
angle parameter to be provided in radians (values from 0 to
* 6.28). Values are returned in the range -1 to 1.
*
* @webref math:trigonometry
* @webBrief Calculates the sine of an angle
* @param angle an angle in radians
* @see PApplet#cos(float)
* @see PApplet#tan(float)
* @see PApplet#radians(float)
*/
static public final float sin(float angle) {
return (float)Math.sin(angle);
}
/**
*
* Calculates the cosine of an angle. This function expects the values of
* the
angle parameter to be provided in radians (values from 0 to
* PI*2). Values are returned in the range -1 to 1.
*
* @webref math:trigonometry
* @webBrief Calculates the cosine of an angle
* @param angle an angle in radians
* @see PApplet#sin(float)
* @see PApplet#tan(float)
* @see PApplet#radians(float)
*/
static public final float cos(float angle) {
return (float)Math.cos(angle);
}
/**
*
* Calculates the ratio of the sine and cosine of an angle. This function
* expects the values of the
angle parameter to be provided in
* radians (values from 0 to PI*2). Values are returned in the range
*
infinity to
-infinity.
*
* @webref math:trigonometry
* @webBrief Calculates the ratio of the sine and cosine of an angle
* @param angle an angle in radians
* @see PApplet#cos(float)
* @see PApplet#sin(float)
* @see PApplet#radians(float)
*/
static public final float tan(float angle) {
return (float)Math.tan(angle);
}
/**
*
* The inverse of
sin(), returns the arc sine of a value. This
* function expects the values in the range of -1 to 1 and values are
* returned in the range
-PI/2 to
PI/2.
*
* @webref math:trigonometry
* @webBrief The inverse of
sin(), returns the arc sine of a value
* @param value the value whose arc sine is to be returned
* @see PApplet#sin(float)
* @see PApplet#acos(float)
* @see PApplet#atan(float)
*/
static public final float asin(float value) {
return (float)Math.asin(value);
}
/**
*
* The inverse of
cos(), returns the arc cosine of a value. This
* function expects the values in the range of -1 to 1 and values are
* returned in the range
0 to
PI (3.1415927).
*
* @webref math:trigonometry
* @webBrief The inverse of
cos(), returns the arc cosine of a value
* @param value the value whose arc cosine is to be returned
* @see PApplet#cos(float)
* @see PApplet#asin(float)
* @see PApplet#atan(float)
*/
static public final float acos(float value) {
return (float)Math.acos(value);
}
/**
*
* The inverse of
tan(), returns the arc tangent of a value. This
* function expects the values in the range of -Infinity to Infinity
* (exclusive) and values are returned in the range
-PI/2 to
PI/2 .
*
* @webref math:trigonometry
* @webBrief The inverse of
tan(), returns the arc tangent of a value
* @param value -Infinity to Infinity (exclusive)
* @see PApplet#tan(float)
* @see PApplet#asin(float)
* @see PApplet#acos(float)
*/
static public final float atan(float value) {
return (float)Math.atan(value);
}
/**
*
* Calculates the angle (in radians) from a specified point to the
* coordinate origin as measured from the positive x-axis. Values are
* returned as a
float in the range from
PI to
-PI.
* The
atan2() function is most often used for orienting geometry to
* the position of the cursor. Note: The y-coordinate of the point is the
* first parameter and the x-coordinate is the second due the structure
* of calculating the tangent.
*
* @webref math:trigonometry
* @webBrief Calculates the angle (in radians) from a specified point to the
* coordinate origin as measured from the positive x-axis
* @param y y-coordinate of the point
* @param x x-coordinate of the point
* @see PApplet#tan(float)
*/
static public final float atan2(float y, float x) {
return (float)Math.atan2(y, x);
}
/**
*
* Converts a radian measurement to its corresponding value in degrees.
* Radians and degrees are two ways of measuring the same thing. There are
* 360 degrees in a circle and 2*PI radians in a circle. For example,
* 90° = PI/2 = 1.5707964. All trigonometric functions in Processing
* require their parameters to be specified in radians.
*
* @webref math:trigonometry
* @webBrief Converts a radian measurement to its corresponding value in degrees
* @param radians radian value to convert to degrees
* @see PApplet#radians(float)
*/
static public final float degrees(float radians) {
return radians * RAD_TO_DEG;
}
/**
*
* Converts a degree measurement to its corresponding value in radians.
* Radians and degrees are two ways of measuring the same thing. There are
* 360 degrees in a circle and 2*PI radians in a circle. For example,
* 90° = PI/2 = 1.5707964. All trigonometric functions in Processing
* require their parameters to be specified in radians.
*
* @webref math:trigonometry
* @webBrief Converts a degree measurement to its corresponding value in radians
* @param degrees degree value to convert to radians
* @see PApplet#degrees(float)
*/
static public final float radians(float degrees) {
return degrees * DEG_TO_RAD;
}
/**
*
* Calculates the closest int value that is greater than or equal to the
* value of the parameter. For example,
ceil(9.03) returns the value 10.
*
* @webref math:calculation
* @webBrief Calculates the closest int value that is greater than or equal to the
* value of the parameter
* @param n number to round up
* @see PApplet#floor(float)
* @see PApplet#round(float)
*/
static public final int ceil(float n) {
return (int) Math.ceil(n);
}
/**
*
* Calculates the closest int value that is less than or equal to the value
* of the parameter.
*
* @webref math:calculation
* @webBrief Calculates the closest int value that is less than or equal to the value
* of the parameter
* @param n number to round down
* @see PApplet#ceil(float)
* @see PApplet#round(float)
*/
static public final int floor(float n) {
return (int) Math.floor(n);
}
/**
*
* Calculates the integer closest to the
n parameter. For example,
*
round(133.8) returns the value 134.
*
* @webref math:calculation
* @webBrief Calculates the integer closest to the
value parameter
* @param n
* number to round
* @see PApplet#floor(float)
* @see PApplet#ceil(float)
*/
static public final int round(float n) {
return Math.round(n);
}
static public final float mag(float a, float b) {
return (float)Math.sqrt(a*a + b*b);
}
/**
*
* Calculates the magnitude (or length) of a vector. A vector is a
* direction in space commonly used in computer graphics and linear
* algebra. Because it has no "start" position, the magnitude of a vector
* can be thought of as the distance from coordinate (0,0) to its (x,y)
* value. Therefore,
mag() is a shortcut for writing
dist(0, 0, x, y).
*
* @webref math:calculation
* @webBrief Calculates the magnitude (or length) of a vector
* @param a first value
* @param b second value
* @param c third value
* @see PApplet#dist(float, float, float, float)
*/
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));
}
/**
*
* Calculates the distance between two points.
*
* @webref math:calculation
* @webBrief Calculates the distance between two points
* @param x1 x-coordinate of the first point
* @param y1 y-coordinate of the first point
* @param z1 z-coordinate of the first point
* @param x2 x-coordinate of the second point
* @param y2 y-coordinate of the second point
* @param z2 z-coordinate of the second point
*/
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));
}
/**
*
* Calculates a number between two numbers at a specific increment. The
*
amt parameter is the amount to interpolate between the two values
* where 0.0 equal to the first point, 0.1 is very near the first point,
* 0.5 is half-way in between, etc. The lerp function is convenient for
* creating motion along a straight path and for drawing dotted lines.
*
* @webref math:calculation
* @webBrief Calculates a number between two numbers at a specific increment
* @param start first value
* @param stop second value
* @param amt float between 0.0 and 1.0
* @see PGraphics#curvePoint(float, float, float, float, float)
* @see PGraphics#bezierPoint(float, float, float, float, float)
* @see PVector#lerp(PVector, float)
* @see PGraphics#lerpColor(int, int, float)
*/
static public final float lerp(float start, float stop, float amt) {
return start + (stop-start) * amt;
}
/**
*
* Normalizes a number from another range into a value between 0 and 1.
* Identical to
map(value, low, high, 0, 1).
*
* Numbers outside the range are not clamped to 0 and 1, because
* out-of-range values are often intentional and useful. (See the second
* example above.)
*
* @webref math:calculation
* @webBrief Normalizes a number from another range into a value between 0 and
* 1
* @param value
* the incoming value to be converted
* @param start
* lower bound of the value's current range
* @param stop
* upper bound of the value's current range
* @see PApplet#map(float, float, float, float, float)
* @see PApplet#lerp(float, float, float)
*/
static public final float norm(float value, float start, float stop) {
return (value - start) / (stop - start);
}
/**
*
* Re-maps a number from one range to another.
*
* In the first example above, the number 25 is converted from a value in the
* range of 0 to 100 into a value that ranges from the left edge of the window
* (0) to the right edge (width).
*
* As shown in the second example, numbers outside the range are
* not clamped to the minimum and maximum parameters values,
* because out-of-range values are often intentional and useful.
*
* @webref math:calculation
* @webBrief Re-maps a number from one range to another
* @param value
* the incoming value to be converted
* @param start1
* lower bound of the value's current range
* @param stop1
* upper bound of the value's current range
* @param start2
* lower bound of the value's target range
* @param stop2
* upper bound of the value's target range
* @see PApplet#norm(float, float, float)
* @see PApplet#lerp(float, float, float)
*/
static public final float map(float value,
float start1, float stop1,
float start2, float stop2) {
float outgoing =
start2 + (stop2 - start2) * ((value - start1) / (stop1 - start1));
String badness = null;
if (outgoing != outgoing) {
badness = "NaN (not a number)";
} else if (outgoing == Float.NEGATIVE_INFINITY ||
outgoing == Float.POSITIVE_INFINITY) {
badness = "infinity";
}
if (badness != null) {
final String msg =
String.format("map(%s, %s, %s, %s, %s) called, which returns %s",
nf(value), nf(start1), nf(stop1),
nf(start2), nf(stop2), badness);
PGraphics.showWarning(msg);
}
return outgoing;
}
/*
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;
/**
*
*/
public final float random(float high) {
// avoid an infinite loop when 0 or NaN are passed in
if (high == 0 || high != high) {
return 0;
}
if (internalRandom == null) {
internalRandom = new Random();
}
// 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'
float value;
do {
value = internalRandom.nextFloat() * high;
} while (value == high);
return value;
}
/**
*
* Returns a float from a random series of numbers having a mean of 0
* and standard deviation of 1. Each time the
randomGaussian()
* function is called, it returns a number fitting a Gaussian, or
* normal, distribution. There is theoretically no minimum or maximum
* value that
randomGaussian() might return. Rather, there is
* just a very low probability that values far from the mean will be
* returned; and a higher probability that numbers near the mean will
* be returned.
*
* @webref math:random
* @webBrief Returns a float from a random series of numbers having a mean of 0
* and standard deviation of 1
* @see PApplet#random(float,float)
* @see PApplet#noise(float, float, float)
*/
public final float randomGaussian() {
if (internalRandom == null) {
internalRandom = new Random();
}
return (float) internalRandom.nextGaussian();
}
/**
*
* Generates random numbers. Each time the
random() function is called,
* it returns an unexpected value within the specified range. If only one
* parameter is passed to the function, it will return a float between zero
* and the value of the
high parameter. For example,
random(5)
* returns values between 0 and 5 (starting at zero, and up to, but not
* including, 5).
*
* If two parameters are specified, the function will return a float with a
* value between the two values. For example,
random(-5, 10.2) returns
* values starting at -5 and up to (but not including) 10.2. To convert a
* floating-point random number to an integer, use the
int() function.
*
* @webref math:random
* @webBrief Generates random numbers
* @param low
* lower limit
* @param high
* upper limit
* @see PApplet#randomSeed(long)
* @see PApplet#noise(float, float, float)
*/
public final float random(float low, float high) {
if (low >= high) return low;
float diff = high - low;
float value;
// because of rounding error, can't just add low, otherwise it may hit high
// https://github.com/processing/processing/issues/4551
do {
value = random(diff) + low;
} while (value == high);
return value;
}
/**
*
* Sets the seed value for
random(). By default,
random()
* produces different results each time the program is run. Set the
seed
* parameter to a constant to return the same pseudo-random numbers each time
* the software is run.
*
* @webref math:random
* @webBrief Sets the seed value for
random()
* @param seed
* seed value
* @see PApplet#random(float,float)
* @see PApplet#noise(float, float, float)
* @see PApplet#noiseSeed(long)
*/
public final void randomSeed(long seed) {
if (internalRandom == null) {
internalRandom = new Random();
}
internalRandom.setSeed(seed);
}
/**
* Return a random integer from 0 up to (but not including)
* the specified value for “high”. This is the same as calling random()
* and casting the result to an
int.
*/
public final int choice(int high) {
return (int) random(high);
}
/**
* Return a random integer from “low” up to (but not including)
* the specified value for “high”. This is the same as calling random()
* and casting the result to an
int.
*/
public final int choice(int low, int high) {
return (int) random(low, high);
}
//////////////////////////////////////////////////////////////
// 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>= 1;
}
if (x new Thread(r, REQUEST_IMAGE_THREAD_PREFIX);
requestImagePool = Executors.newFixedThreadPool(4, factory);
}
requestImagePool.execute(() -> {
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;
vessel.pixelWidth = actual.width;
vessel.pixelHeight = actual.height;
vessel.pixelDensity = 1;
}
});
return vessel;
}
//////////////////////////////////////////////////////////////
// DATA I/O
/**
* Reads the contents of a file or URL and creates an XML
* object with its values. If a file is specified, it must
* be located in the sketch's "data" folder. The filename
* parameter can also be a URL to a file found online.
* All files loaded and saved by the Processing API use
* UTF-8 encoding. If you need to load an XML file that's
* not in UTF-8 format, see the
* developer's reference for the XML object.
* @webref input:files
* @webBrief Reads the contents of a file or URL and creates an
XML
* object with its values
* @param filename name of a file in the data folder or a URL.
* @see XML
* @see PApplet#parseXML(String)
* @see PApplet#saveXML(XML, String)
* @see PApplet#loadBytes(String)
* @see PApplet#loadStrings(String)
* @see PApplet#loadTable(String)
*/
public XML loadXML(String filename) {
return loadXML(filename, null);
}
// version that uses 'options' though there are currently no supported options
/**
* @nowebref
*/
public XML loadXML(String filename, String options) {
try {
BufferedReader reader = createReader(filename);
if (reader != null) {
return new XML(reader, options);
}
return null;
// can't use catch-all exception, since it might catch the
// RuntimeException about the incorrect case sensitivity
} catch (IOException | ParserConfigurationException | SAXException e) {
throw new RuntimeException(e);
}
}
/**
* Takes a String, parses its contents, and returns an XML object. If the
* String does not contain XML data or cannot be parsed, a
null value is
* returned.
*
*
parseXML() is most useful when pulling data dynamically, such as
* from third-party APIs. Normally, API results would be saved to a String,
* and then can be converted to a structured XML object using
*
parseXML(). Be sure to check if
null is returned before performing
* operations on the new XML object, in case the String content could not be
* parsed.
*
* If your data already exists as an XML file in the data folder, it is
* simpler to use
loadXML().
*
* @webref input:files
* @webBrief Converts String content to an
XML object
* @param xmlString
* the content to be parsed as XML
* @return an XML object, or null
* @see XML
* @see PApplet#loadXML(String)
* @see PApplet#saveXML(XML, String)
*/
public XML parseXML(String xmlString) {
return parseXML(xmlString, null);
}
public XML parseXML(String xmlString, String options) {
try {
return XML.parse(xmlString, options);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Writes the contents of an XML object to a file. By default, this file is
* saved to the sketch's folder. This folder is opened by selecting "Show
* Sketch Folder" from the "Sketch" menu.
*
* Alternatively, the file can be saved to any location on the computer by
* using an absolute path (something that starts with / on Unix and Linux, or
* a drive letter on Windows).
*
* All files loaded and saved by the Processing API use UTF-8 encoding.
*
* @webref output:files
* @webBrief Writes the contents of an
XML object to a file
* @param xml
* the XML object to save to disk
* @param filename
* name of the file to write to
* @see XML
* @see PApplet#loadXML(String)
* @see PApplet#parseXML(String)
*/
public boolean saveXML(XML xml, String filename) {
return saveXML(xml, filename, null);
}
/**
* @nowebref
*/
public boolean saveXML(XML xml, String filename, String options) {
return xml.save(saveFile(filename), options);
}
/**
* Takes a
String, parses its contents, and returns a
*
JSONObject. If the
String does not contain
JSONObject
* data or cannot be parsed, a
null value is returned.
*
*
parseJSONObject() is most useful when pulling data dynamically, such
* as from third-party APIs. Normally, API results would be saved to a
*
String, and then can be converted to a structured
JSONObject
* using
parseJSONObject(). Be sure to check if
null is returned
* before performing operations on the new
JSONObject in case the
*
String content could not be parsed.
*
* If your data already exists as a
JSON file in the data folder, it is
* simpler to use
loadJSONObject().
*
* @webref input:files
* @webBrief Takes a
String, parses its contents, and returns a
*
JSONObject
* @param input
* String to parse as a JSONObject
* @see PApplet#loadJSONObject(String)
* @see PApplet#saveJSONObject(JSONObject, String)
*/
public JSONObject parseJSONObject(String input) {
try {
return new JSONObject(new StringReader(input));
} catch (RuntimeException e) {
e.printStackTrace();
return null;
}
}
/**
* Loads a JSON from the data folder or a URL, and returns a
*
JSONObject.
*
* All files loaded and saved by the Processing API use UTF-8 encoding.
*
* @webref input:files
* @webBrief Loads a JSON from the data folder or a URL, and returns a
*
JSONObject
* @param filename
* name of a file in the data folder or a URL
* @see JSONObject
* @see JSONArray
* @see PApplet#loadJSONArray(String)
* @see PApplet#saveJSONObject(JSONObject, String)
* @see PApplet#saveJSONArray(JSONArray, String)
*/
public JSONObject loadJSONObject(String filename) {
// can't pass of createReader() to the constructor b/c of resource leak
BufferedReader reader = createReader(filename);
if (reader != null) {
JSONObject outgoing = new JSONObject(reader);
try {
reader.close();
} catch (IOException e) { // not sure what would cause this
e.printStackTrace();
}
return outgoing;
}
return null;
}
/**
* @nowebref
*/
static public JSONObject loadJSONObject(File file) {
// can't pass of createReader() to the constructor b/c of resource leak
BufferedReader reader = createReader(file);
JSONObject outgoing = new JSONObject(reader);
try {
reader.close();
} catch (IOException e) { // not sure what would cause this
e.printStackTrace();
}
return outgoing;
}
/**
* Writes the contents of a
JSONObject object to a file. By default,
* this file is saved to the sketch's folder. This folder is opened by
* selecting "Show Sketch Folder" from the "Sketch" menu.
*
* Alternatively, the file can be saved to any location on the computer by
* using an absolute path (something that starts with / on Unix and Linux, or
* a drive letter on Windows).
*
* All files loaded and saved by the Processing API use UTF-8 encoding.
*
* @webref output:files
* @webBrief Writes the contents of a
JSONObject object to a file
* @param json
* the JSONObject to save
* @param filename
* the name of the file to save to
* @see JSONObject
* @see JSONArray
* @see PApplet#loadJSONObject(String)
* @see PApplet#loadJSONArray(String)
* @see PApplet#saveJSONArray(JSONArray, String)
*/
public boolean saveJSONObject(JSONObject json, String filename) {
return saveJSONObject(json, filename, null);
}
/**
* @param options "compact" and "indent=N", replace N with the number of spaces
*/
public boolean saveJSONObject(JSONObject json, String filename, String options) {
return json.save(saveFile(filename), options);
}
/**
* Takes a
String, parses its contents, and returns a
JSONArray.
* If the
String does not contain
JSONArray data or cannot be
* parsed, a
null value is returned.
*
*
parseJSONArray() is most useful when pulling data dynamically, such as
* from third-party APIs. Normally, API results would be saved to a
*
String, and then can be converted to a structured
JSONArray
* using
parseJSONArray(). Be sure to check if
null is returned
* before performing operations on the new
JSONArray in case the
*
String content could not be parsed.
*
* If your data already exists as a
JSON file in the data folder, it is
* simpler to use
loadJSONArray().
*
* @webref input:files
* @webBrief Takes a
String, parses its contents, and returns a
JSONArray
* @param input
* String to parse as a JSONArray
* @see JSONObject
* @see PApplet#loadJSONObject(String)
* @see PApplet#saveJSONObject(JSONObject, String)
*/
public JSONArray parseJSONArray(String input) {
try {
return new JSONArray(new StringReader(input));
} catch (RuntimeException e) {
e.printStackTrace();
return null;
}
}
/**
* Loads an array of JSON objects from the data folder or a URL, and returns a
*
JSONArray. Per standard JSON syntax, the array must be enclosed in a
* pair of hard brackets
[], and each object within the array must be
* separated by a comma.
*
* All files loaded and saved by the Processing API use UTF-8 encoding.
*
* @webref input:files
* @webBrief Takes a
String, parses its contents, and returns a
*
JSONArray
* @param filename
* name of a file in the data folder or a URL
* @see JSONArray
* @see PApplet#loadJSONObject(String)
* @see PApplet#saveJSONObject(JSONObject, String)
* @see PApplet#saveJSONArray(JSONArray, String)
*/
public JSONArray loadJSONArray(String filename) {
// can't pass of createReader() to the constructor b/c of resource leak
BufferedReader reader = createReader(filename);
if (reader != null) {
JSONArray outgoing = new JSONArray(reader);
try {
reader.close();
} catch (IOException e) { // not sure what would cause this
e.printStackTrace();
}
return outgoing;
}
return null;
}
static public JSONArray loadJSONArray(File file) {
// can't pass of createReader() to the constructor b/c of resource leak
BufferedReader reader = createReader(file);
JSONArray outgoing = new JSONArray(reader);
try {
reader.close();
} catch (IOException e) { // not sure what would cause this
e.printStackTrace();
}
return outgoing;
}
/**
* Writes the contents of a
JSONArray object to a file. By default,
* this file is saved to the sketch's folder. This folder is opened by
* selecting "Show Sketch Folder" from the "Sketch" menu.
*
* Alternatively, the file can be saved to any location on the computer by
* using an absolute path (something that starts with / on Unix and Linux, or
* a drive letter on Windows).
*
* All files loaded and saved by the Processing API use UTF-8 encoding.
*
* @webref output:files
* @webBrief Writes the contents of a
JSONArray object to a file
* @param json
* the JSONArray to save
* @param filename
* the name of the file to save to
* @see JSONObject
* @see JSONArray
* @see PApplet#loadJSONObject(String)
* @see PApplet#loadJSONArray(String)
* @see PApplet#saveJSONObject(JSONObject, String)
*/
public boolean saveJSONArray(JSONArray json, String filename) {
return saveJSONArray(json, filename, null);
}
/**
* @param options "compact" and "indent=N", replace N with the number of spaces
*/
public boolean saveJSONArray(JSONArray json, String filename, String options) {
return json.save(saveFile(filename), options);
}
/**
* Reads the contents of a file or URL and creates a Table object with its
* values. If a file is specified, it must be located in the sketch's "data"
* folder. The filename parameter can also be a URL to a file found online.
* The filename must either end in an extension or an extension must be
* specified in the
options parameter. For example, to use
* tab-separated data, include "tsv" in the options parameter if the filename
* or URL does not end in
.tsv. Note: If an extension is in both
* places, the extension in the
options is used.
*
* If the file contains a header row, include "header" in the
options
* parameter. If the file does not have a header row, then simply omit the
* "header" option.
*
* Some CSV files contain newline (CR or LF) characters inside cells. This is
* rare, but adding the "newlines" option will handle them properly. (This is
* not enabled by default because the parsing code is much slower.)
*
* When specifying multiple options, separate them with commas, as in:
*
loadTable("data.csv", "header, tsv")
*
* All files loaded and saved by the Processing API use UTF-8 encoding.
*
* @webref input:files
* @webBrief Reads the contents of a file or URL and creates a
Table object
* with its values
* @param filename
* name of a file in the data folder or a URL.
* @see Table
* @see PApplet#saveTable(Table, String)
* @see PApplet#loadBytes(String)
* @see PApplet#loadStrings(String)
* @see PApplet#loadXML(String)
*/
public Table loadTable(String filename) {
return loadTable(filename, null);
}
/**
* Options may contain "header", "tsv", "csv", or "bin" separated by commas.
*
* Another option is "dictionary=filename.tsv", which allows users to
* specify a "dictionary" file that contains a mapping of the column titles
* and the data types used in the table file. This can be far more efficient
* (in terms of speed and memory usage) for loading and parsing tables. The
* dictionary file can only be tab-separated values (.tsv) and its extension
* will be ignored. This option was added in Processing 2.0.2.
*
* @param options may contain "header", "tsv", "csv", or "bin" separated by commas
*/
public Table loadTable(String filename, String options) {
try {
String optionStr = Table.extensionOptions(true, filename, options);
String[] optionList = trim(split(optionStr, ','));
for (String opt : optionList) {
if (opt.startsWith("dictionary=")) {
Table dictionary = loadTable(opt.substring(opt.indexOf('=') + 1), "tsv");
return dictionary.typedParse(createInput(filename), optionStr);
}
}
InputStream input = createInput(filename);
if (input == null) {
System.err.println(filename + " does not exist or could not be read");
return null;
}
return new Table(input, optionStr);
} catch (IOException e) {
printStackTrace(e);
return null;
}
}
/**
* Writes the contents of a Table object to a file. By default, this file is
* saved to the sketch's folder. This folder is opened by selecting "Show
* Sketch Folder" from the "Sketch" menu.
*
* Alternatively, the file can be saved to any location on the computer by
* using an absolute path (something that starts with / on Unix and Linux, or
* a drive letter on Windows).
*
* All files loaded and saved by the Processing API use UTF-8 encoding.
*
* @webref output:files
* @webBrief Writes the contents of a
Table object to a file
* @param table
* the Table object to save to a file
* @param filename
* the filename to which the Table should be saved
* @see Table
* @see PApplet#loadTable(String)
*/
public boolean saveTable(Table table, String filename) {
return saveTable(table, filename, null);
}
/**
* @param options can be one of "tsv", "csv", "bin", or "html"
*/
public boolean saveTable(Table table, String filename, String options) {
// String ext = checkExtension(filename);
// if (ext != null) {
// if (ext.equals("csv") || ext.equals("tsv") || ext.equals("bin") || ext.equals("html")) {
// if (options == null) {
// options = ext;
// } else {
// options = ext + "," + options;
// }
// }
// }
try {
// Figure out location and make sure the target path exists
File outputFile = saveFile(filename);
// Open a stream and take care of .gz if necessary
return table.save(outputFile, options);
} catch (IOException e) {
printStackTrace(e);
return false;
}
}
//////////////////////////////////////////////////////////////
// FONT I/O
/**
*
* Loads a .vlw formatted font into a
PFont object. Create a .vlw font
* by selecting "Create Font..." from the Tools menu. This tool creates a
* texture for each alphanumeric character and then adds them as a .vlw file
* to the current sketch's data folder. Because the letters are defined as
* textures (and not vector data) the size at which the fonts are created must
* be considered in relation to the size at which they are drawn. For example,
* load a 32pt font if the sketch displays the font at 32 pixels or smaller.
* Conversely, if a 12pt font is loaded and displayed at 48pts, the letters
* will be distorted because the program will be stretching a small graphic to
* a large size.
*
* Like
loadImage() and other functions that load data, the
*
loadFont() function should not be used inside
draw(), because
* it will slow down the sketch considerably, as the font will be re-loaded
* from the disk (or network) on each frame. It's recommended to load files
* inside
setup()
*
* To load correctly, fonts must be located in the "data" folder of the
* current sketch. Alternatively, the file maybe be loaded from anywhere on
* the local computer using an absolute path (something that starts with / on
* Unix and Linux, or a drive letter on Windows), or the filename parameter
* can be a URL for a file found on a network.
*
* If the file is not available or an error occurs,
null will be
* returned and an error message will be printed to the console. The error
* message does not halt the program, however the
null value may cause a
* NullPointerException if your code does not check whether the value returned
* is
null.
*
* Use
createFont() (instead of
loadFont()) to enable vector
* data to be used with the default renderer setting. This can be helpful when
* many font sizes are needed, or when using any renderer based on the default
* renderer, such as the PDF library.
*
* @webref typography:loading & displaying
* @webBrief Loads a font into a variable of type
PFont
* @param filename
* name of the font to load
* @see PFont
* @see PGraphics#textFont(PFont, float)
* @see PApplet#createFont(String, float, boolean, char[])
*/
public PFont loadFont(String filename) {
if (!filename.toLowerCase().endsWith(".vlw")) {
throw new IllegalArgumentException("loadFont() is for .vlw files, try createFont()");
}
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, null);
}
public PFont createFont(String name, float size, boolean smooth) {
return createFont(name, size, smooth, null);
}
/**
*
* Dynamically converts a font to the format used by Processing from a .ttf or
* .otf file inside the sketch's "data" folder or a font that's installed
* elsewhere on the computer. If you want to use a font installed on your
* computer, use the
PFont.list() method to first determine the names
* for the fonts recognized by the computer and are compatible with this
* function. Not all fonts can be used and some might work with one operating
* system and not others. When sharing a sketch with other people or posting
* it on the web, you may need to include a .ttf or .otf version of your font
* in the data directory of the sketch because other people might not have the
* font installed on their computer. Only fonts that can legally be
* distributed should be included with a sketch.
*
* The
size parameter states the font size you want to generate. The
*
smooth parameter specifies if the font should be anti-aliased or not.
* The
charset parameter is an array of chars that specifies the
* characters to generate.
*
* This function allows Processing to work with the font natively in the
* default renderer, so the letters are defined by vector geometry and are
* rendered quickly. In the
P2D and
P3D renderers, the function
* sets the project to render the font as a series of small textures. For
* instance, when using the default renderer, the actual native version of the
* font will be employed by the sketch, improving drawing quality and
* performance. With the
P2D and
P3D renderers, the bitmapped
* version will be used to improve speed and appearance, but the results are
* poor when exporting if the sketch does not include the .otf or .ttf file,
* and the requested font is not available on the machine running the sketch.
*
* @webref typography:loading & displaying
* @webBrief Dynamically converts a font to the format used by Processing
* @param name
* name of the font to load
* @param size
* point size of the font
* @param smooth
* true for an anti-aliased font, false for aliased
* @param charset
* array containing characters to be generated
* @see PFont
* @see PGraphics#textFont(PFont, float)
* @see PGraphics#text(String, float, float, float, float)
* @see PApplet#loadFont(String)
*/
public PFont createFont(String name, float size,
boolean smooth, char[] charset) {
if (g == null) {
throw new RuntimeException("createFont() can only be used inside setup() or after setup() has been called.");
}
return g.createFont(name, size, smooth, charset);
}
//////////////////////////////////////////////////////////////
// FILE/FOLDER SELECTION
/**
* Open a platform-specific file chooser dialog to select a file for input.
* After the selection is made, the selected File will be passed to the
* 'callback' function. If the dialog is closed or canceled,
null will be sent
* to the function, so that the program is not waiting for additional input.
* The callback is necessary because of how threading works.
*
*
Advanced
*
* void setup() {
* selectInput("Select a file to process:", "fileSelected");
* }
*
* void fileSelected(File selection) {
* if (selection == null) {
* println("Window was closed or the user hit cancel.");
* } else {
* println("User selected " + fileSelected.getAbsolutePath());
* }
* }
*
*
* For advanced users, the method must be 'public', which is true for all
* methods inside a sketch when run from the PDE, but must explicitly be set
* when using Eclipse or other development environments.
*
* @webref input:files
* @webBrief Open a platform-specific file chooser dialog to select a file for
* input
* @param prompt
* message to the user
* @param callback
* name of the method to be called when the selection is made
*/
public void selectInput(String prompt, String callback) {
selectInput(prompt, callback, null);
}
public void selectInput(String prompt, String callback, File file) {
selectInput(prompt, callback, file, this);
}
public void selectInput(String prompt, String callback,
File file, Object callbackObject) {
//selectInput(prompt, callback, file, callbackObject, null, this);
surface.selectInput(prompt, callback, file, callbackObject);
}
/**
* Opens a platform-specific file chooser dialog to select a file for output.
* After the selection is made, the selected File will be passed to the
* 'callback' function. If the dialog is closed or canceled,
null will be sent
* to the function, so that the program is not waiting for additional input.
* The callback is necessary because of how threading works.
*
* @webref output:files
* @webBrief Opens a platform-specific file chooser dialog to select a file for output
* @param prompt message to the user
* @param callback name of the method to be called when the selection is made
*/
public void selectOutput(String prompt, String callback) {
selectOutput(prompt, callback, null);
}
public void selectOutput(String prompt, String callback, File file) {
selectOutput(prompt, callback, file, this);
}
public void selectOutput(String prompt, String callback,
File file, Object callbackObject) {
//selectOutput(prompt, callback, file, callbackObject, null, this);
surface.selectOutput(prompt, callback, file, callbackObject);
}
/**
* Opens a platform-specific file chooser dialog to select a folder.
* After the selection is made, the selection will be passed to the
* 'callback' function. If the dialog is closed or canceled, null
* will be sent to the function, so that the program is not waiting
* for additional input. The callback is necessary because of how
* threading works.
*
* @webref input:files
* @webBrief Opens a platform-specific file chooser dialog to select a folder
* @param prompt message to the user
* @param callback name of the method to be called when the selection is made
*/
public void selectFolder(String prompt, String callback) {
selectFolder(prompt, callback, null);
}
public void selectFolder(String prompt, String callback, File file) {
selectFolder(prompt, callback, file, this);
}
public void selectFolder(String prompt, String callback,
File file, Object callbackObject) {
//selectFolder(prompt, callback, file, callbackObject, null, this);
surface.selectFolder(prompt, callback, file, callbackObject);
}
static public void selectCallback(File selectedFile,
String callbackMethod,
Object callbackObject) {
try {
Class callbackClass = callbackObject.getClass();
Method selectMethod =
callbackClass.getMethod(callbackMethod, File.class);
selectMethod.invoke(callbackObject, selectedFile);
} catch (IllegalAccessException iae) {
System.err.println(callbackMethod + "() must be public");
} catch (InvocationTargetException ite) {
ite.printStackTrace();
} catch (NoSuchMethodException nsme) {
System.err.println(callbackMethod + "() could not be found");
}
}
//////////////////////////////////////////////////////////////
// LISTING DIRECTORIES
public String[] listPaths(String path, String... options) {
File[] list = listFiles(path, options);
int offset = 0;
for (String opt : options) {
if (opt.equals("relative")) {
if (!path.endsWith(File.pathSeparator)) {
path += File.pathSeparator;
}
offset = path.length();
break;
}
}
String[] outgoing = new String[list.length];
for (int i = 0; i < list.length; i++) {
// as of Java 1.8, substring(0) returns the original object
outgoing[i] = list[i].getAbsolutePath().substring(offset);
}
return outgoing;
}
public File[] listFiles(String path, String... options) {
File file = new File(path);
// if not an absolute path, make it relative to the sketch folder
if (!file.isAbsolute()) {
file = sketchFile(path);
}
return listFiles(file, options);
}
// "relative" -> no effect with the Files version, but important for listPaths
// "recursive"
// "extension=js" or "extensions=js|csv|txt" (no dot)
// "directories" -> only directories
// "files" -> only files
// "hidden" -> include hidden files (prefixed with .) disabled by default
static public File[] listFiles(File base, String... options) {
boolean recursive = false;
String[] extensions = null;
boolean directories = true;
boolean files = true;
boolean hidden = false;
for (String opt : options) {
if (opt.equals("recursive")) {
recursive = true;
} else if (opt.startsWith("extension=")) {
extensions = new String[] { opt.substring(10) };
} else if (opt.startsWith("extensions=")) {
extensions = split(opt.substring(11), ',');
} else if (opt.equals("files")) {
directories = false;
} else if (opt.equals("directories")) {
files = false;
} else if (opt.equals("hidden")) {
hidden = true;
} else //noinspection StatementWithEmptyBody
if (opt.equals("relative")) {
// ignored
} else {
throw new RuntimeException(opt + " is not a listFiles() option");
}
}
if (extensions != null) {
for (int i = 0; i < extensions.length; i++) {
extensions[i] = "." + extensions[i];
}
}
if (!files && !directories) {
// just make "only files" and "only directories" mean... both
files = true;
directories = true;
}
if (!base.canRead()) {
return null;
}
List outgoing = new ArrayList();
listFilesImpl(base, recursive, extensions, hidden, directories, files, outgoing);
return outgoing.toArray(new File[0]);
}
static private boolean listFilesExt(String name, String[] extensions) {
for (String ext : extensions) {
if (name.toLowerCase().endsWith(ext)) {
return true;
}
}
return false;
}
static void listFilesImpl(File folder, boolean recursive,
String[] extensions, boolean hidden,
boolean directories, boolean files,
List list) {
File[] items = folder.listFiles();
if (items != null) {
for (File item : items) {
String name = item.getName();
if (!hidden && name.charAt(0) == '.') {
continue;
}
if (item.isDirectory()) {
if (recursive) {
listFilesImpl(item, recursive, extensions, hidden, directories, files, list);
}
if (directories) {
if (extensions == null || listFilesExt(item.getName(), extensions)) {
list.add(item);
}
}
} else if (files) {
if (extensions == null || listFilesExt(item.getName(), extensions)) {
list.add(item);
}
}
}
}
}
//////////////////////////////////////////////////////////////
// EXTENSIONS
/**
* Get the compression-free extension for this filename.
* @param filename The filename to check
* @return an extension, skipping past .gz if it's present
*/
static public String checkExtension(String filename) {
// Don't consider the .gz as part of the name, createInput()
// and createOutput() will take care of fixing that up.
if (filename.toLowerCase().endsWith(".gz")) {
filename = filename.substring(0, filename.length() - 3);
}
int dotIndex = filename.lastIndexOf('.');
if (dotIndex != -1) {
return filename.substring(dotIndex + 1).toLowerCase();
}
return null;
}
//////////////////////////////////////////////////////////////
// READERS AND WRITERS
/**
*
* Creates a
BufferedReader object that can be used to read files
* line-by-line as individual
String objects. This is the complement to
* the
createWriter() function. For more information about the
*
BufferedReader class and its methods like
readLine() and
*
close used in the above example, please consult a Java
* reference.
*
* Starting with Processing release 0134, all files loaded and saved by the
* Processing API use UTF-8 encoding. In previous releases, the default
* encoding for your platform was used, which causes problems when files are
* moved to other platforms.
*
* @webref input:files
* @webBrief Creates a
BufferedReader object that can be used to read
* files line-by-line as individual
String objects
* @param filename
* name of the file to be opened
* @see BufferedReader
* @see PApplet#createWriter(String)
* @see PrintWriter
*/
public BufferedReader createReader(String filename) {
InputStream is = createInput(filename);
if (is == null) {
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;
}
return createReader(is);
}
/**
* @nowebref
*/
static public BufferedReader createReader(File file) {
try {
InputStream is = new FileInputStream(file);
if (file.getName().toLowerCase().endsWith(".gz")) {
is = new GZIPInputStream(is);
}
return createReader(is);
} catch (IOException e) {
// Re-wrap rather than forcing novices to learn about exceptions
throw new RuntimeException(e);
}
}
/**
* @nowebref
* I want to read lines from a stream. If I have to type the
* following lines anymore I'm gonna send Sun my medical bills.
*/
static public BufferedReader createReader(InputStream input) {
InputStreamReader isr =
new InputStreamReader(input, StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(isr);
// consume the Unicode BOM (byte order marker) if present
try {
reader.mark(1);
int c = reader.read();
// if not the BOM, back up to the beginning again
if (c != '\uFEFF') {
reader.reset();
}
} catch (IOException e) {
e.printStackTrace();
}
return reader;
}
/**
*
* Creates a new file in the sketch folder, and a
PrintWriter object
* to write to it. For the file to be made correctly, it should be flushed
* and must be closed with its
flush() and
close() methods
* (see above example).
*
* Starting with Processing release 0134, all files loaded and saved by the
* Processing API use UTF-8 encoding. In previous releases, the default
* encoding for your platform was used, which causes problems when files
* are moved to other platforms.
*
* @webref output:files
* @webBrief Creates a new file in the sketch folder, and a
PrintWriter object
* to write to it
* @param filename name of the file to be created
* @see PrintWriter
* @see PApplet#createReader
* @see BufferedReader
*/
public PrintWriter createWriter(String filename) {
return createWriter(saveFile(filename));
}
/**
* @nowebref
* I want to print lines to a file. I have RSI from typing these
* eight lines of code so many times.
*/
static public PrintWriter createWriter(File file) {
if (file == null) {
throw new RuntimeException("File passed to createWriter() was null");
}
try {
createPath(file); // make sure in-between folders exist
OutputStream output = new FileOutputStream(file);
if (file.getName().toLowerCase().endsWith(".gz")) {
output = new GZIPOutputStream(output);
}
return createWriter(output);
} catch (Exception e) {
throw new RuntimeException("Couldn't create a writer for " +
file.getAbsolutePath(), e);
}
}
/**
* @nowebref
* I want to print lines to a file. Why am I always explaining myself?
* It's the JavaSoft API engineers who need to explain themselves.
*/
static public PrintWriter createWriter(OutputStream output) {
BufferedOutputStream bos = new BufferedOutputStream(output, 8192);
OutputStreamWriter osw =
new OutputStreamWriter(bos, StandardCharsets.UTF_8);
return new PrintWriter(osw);
}
//////////////////////////////////////////////////////////////
// FILE INPUT
/**
* This is a function for advanced programmers to open a Java InputStream.
* It's useful if you want to use the facilities provided by PApplet to
* easily open files from the data folder or from a URL, but want an
* InputStream object so that you can use other parts of Java to take more
* control of how the stream is read.
*
* The filename passed in can be:
* - A URL, for instance
openStream("http://processing.org/")
* - A file in the sketch's
data folder
* - The full path to a file to be opened locally (when running as an
* application)
*
* If the requested item doesn't exist,
null is returned. If not online,
* this will also check to see if the user is asking for a file whose name
* isn't properly capitalized. If capitalization is different, an error
* will be printed to the console. This helps prevent issues that appear
* when a sketch is exported to the web, where case sensitivity matters, as
* opposed to running from inside the Processing Development Environment on
* Windows or macOS, where case sensitivity is preserved but ignored.
*
* If the file ends with
.gz, the stream will automatically be gzip
* decompressed. If you don't want the automatic decompression, use the
* related function
createInputRaw().
*
* In earlier releases, this function was called
openStream().
*
*
*
*
Advanced
* Simplified method to open a Java InputStream.
*
* 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 sketch)
*
* 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 macOS 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:
*
* - A URL, for instance openStream("http://processing.org/");
*
- A file in the sketch's data folder
*
- Another file to be opened locally (when running as an application)
*
*
* @webref input:files
* @webBrief This is a function for advanced programmers to open a Java
InputStream
* @param filename the name of the file to use as input
* @see PApplet#createOutput(String)
* @see PApplet#selectOutput(String,String)
* @see PApplet#selectInput(String,String)
*
*/
@SuppressWarnings("JavadocLinkAsPlainText")
public InputStream createInput(String filename) {
InputStream input = createInputRaw(filename);
if (input != null) {
// if it's gzip-encoded, automatically decode
final String lower = filename.toLowerCase();
if (lower.endsWith(".gz") || lower.endsWith(".svgz")) {
try {
// buffered has to go *around* the GZ, otherwise 25x slower
return new BufferedInputStream(new GZIPInputStream(input));
} catch (IOException e) {
printStackTrace(e);
}
} else {
return new BufferedInputStream(input);
}
}
return null;
}
/**
* Call openStream() without automatic gzip decompression.
*/
public InputStream createInputRaw(String filename) {
if (filename == null) return null;
if (sketchPath == null) {
System.err.println("The sketch path is not set.");
throw new RuntimeException("Files must be loaded inside setup() or after it has been called.");
}
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;
}
// First check whether this looks like a URL
if (filename.contains(":")) { // at least smells like URL
try {
URL url = new URL(filename);
URLConnection conn = url.openConnection();
if (conn instanceof HttpURLConnection httpConn) {
// Will not handle a protocol change (see below)
httpConn.setInstanceFollowRedirects(true);
int response = httpConn.getResponseCode();
// Default won't follow HTTP -> HTTPS redirects for security reasons
// http://stackoverflow.com/a/1884427
if (response >= 300 && response < 400) {
String newLocation = httpConn.getHeaderField("Location");
return createInputRaw(newLocation);
}
return conn.getInputStream();
} else if (conn instanceof JarURLConnection) {
return url.openStream();
}
} catch (MalformedURLException mfue) {
// not a URL, that's fine
} catch (FileNotFoundException fnfe) {
// Added in 0119 b/c Java 1.5 throws FNFE when URL not available.
// https://download.processing.org/bugzilla/403.html
} catch (IOException e) {
// changed for 0117, shouldn't be throwing exception
printStackTrace(e);
//System.err.println("Error downloading from URL " + filename);
return null;
//throw new RuntimeException("Error downloading from URL " + filename);
}
}
InputStream stream;
// Moved this earlier than the getResourceAsStream() checks, because
// calling getResourceAsStream() on a directory lists its contents.
// https://download.processing.org/bugzilla/716.html
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 = sketchFile(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 ignored) { }
}
// if this file is ok, may as well just load it
return new FileInputStream(file);
// have to break these out because a general Exception might
// catch the RuntimeException being thrown above
} catch (IOException | SecurityException ignored) { }
// 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!
// https://download.processing.org/bugzilla/359.html
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'.
// https://download.processing.org/bugzilla/389.html
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
try { // first try to catch any security exceptions
try {
return new FileInputStream(dataPath(filename));
} catch (IOException ignored) { }
try {
return new FileInputStream(sketchPath(filename));
} catch (Exception ignored) { }
try {
return new FileInputStream(filename);
} catch (IOException ignored) { }
} catch (SecurityException ignored) { } // online, whups
} catch (Exception e) {
printStackTrace(e);
}
return null;
}
/**
* @nowebref
*/
static public InputStream createInput(File file) {
if (file == null) {
throw new IllegalArgumentException("File passed to createInput() was null");
}
if (!file.exists()) {
System.err.println(file + " does not exist, createInput() will return null");
return null;
}
try {
InputStream input = new FileInputStream(file);
final String lower = file.getName().toLowerCase();
if (lower.endsWith(".gz") || lower.endsWith(".svgz")) {
return new BufferedInputStream(new GZIPInputStream(input));
}
return new BufferedInputStream(input);
} catch (IOException e) {
System.err.println("Could not createInput() for " + file);
e.printStackTrace();
return null;
}
}
/**
*
* Reads the contents of a file and places it in a byte array. If the name of
* the file is used as the parameter, as in the above example, the file must
* be loaded in the sketch's "data" directory/folder.
*
* Alternatively, the file maybe be loaded from anywhere on the local computer
* using an absolute path (something that starts with / on Unix and Linux, or
* a drive letter on Windows), or the filename parameter can be a URL for a
* file found on a network.
*
* If the file is not available or an error occurs,
null will be
* returned and an error message will be printed to the console. The error
* message does not halt the program, however the
null value may cause a
* NullPointerException if your code does not check whether the value returned
* is
null.
*
* @webref input:files
* @webBrief Reads the contents of a file or url and places it in a byte
* array
* @param filename
* name of a file in the data folder or a URL.
* @see PApplet#loadStrings(String)
* @see PApplet#saveStrings(String, String[])
* @see PApplet#saveBytes(String, byte[])
*
*/
public byte[] loadBytes(String filename) {
String lower = filename.toLowerCase();
// If it's not a .gz file, then we might be able to uncompress it into
// a fixed-size buffer, which should help speed because we won't have to
// reallocate and resize the target array each time it gets full.
if (!lower.endsWith(".gz")) {
// If this looks like a URL, try to load it that way. Use the fact that
// URL connections may have a content length header to size the array.
if (filename.contains(":")) { // at least smells like URL
InputStream input = null;
try {
URL url = new URL(filename);
URLConnection conn = url.openConnection();
int length = -1;
if (conn instanceof HttpURLConnection httpConn) {
// Will not handle a protocol change (see below)
httpConn.setInstanceFollowRedirects(true);
int response = httpConn.getResponseCode();
// Default won't follow HTTP -> HTTPS redirects for security reasons
// http://stackoverflow.com/a/1884427
if (response >= 300 && response < 400) {
String newLocation = httpConn.getHeaderField("Location");
return loadBytes(newLocation);
}
length = conn.getContentLength();
input = conn.getInputStream();
} else if (conn instanceof JarURLConnection) {
length = conn.getContentLength();
input = url.openStream();
}
if (input != null) {
byte[] buffer;
if (length != -1) {
buffer = new byte[length];
int count;
int offset = 0;
while ((count = input.read(buffer, offset, length - offset)) > 0) {
offset += count;
}
} else {
buffer = loadBytes(input);
}
input.close();
return buffer;
}
} catch (MalformedURLException mfue) {
// not a url, that's fine
} catch (FileNotFoundException fnfe) {
// Java 1.5+ throws FNFE when URL not available
// https://download.processing.org/bugzilla/403.html
} catch (IOException e) {
printStackTrace(e);
return null;
} finally {
if (input != null) {
try {
input.close();
} catch (IOException e) {
// just deal
}
}
}
}
}
InputStream is = createInput(filename);
if (is != null) {
byte[] outgoing = loadBytes(is);
try {
is.close();
} catch (IOException e) {
printStackTrace(e); // shouldn't happen
}
return outgoing;
}
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;
}
/**
* @nowebref
*/
static public byte[] loadBytes(InputStream input) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int bytesRead = input.read(buffer);
while (bytesRead != -1) {
out.write(buffer, 0, bytesRead);
bytesRead = input.read(buffer);
}
out.flush();
return out.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* @nowebref
*/
static public byte[] loadBytes(File file) {
if (!file.exists()) {
System.err.println(file + " does not exist, loadBytes() will return null");
return null;
}
try {
InputStream input;
int length;
if (file.getName().toLowerCase().endsWith(".gz")) {
RandomAccessFile raf = new RandomAccessFile(file, "r");
raf.seek(raf.length() - 4);
int b4 = raf.read();
int b3 = raf.read();
int b2 = raf.read();
int b1 = raf.read();
length = (b1