diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 96df38386..e83e443c9 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -180,71 +180,7 @@ public class Base { // setup the theme coloring fun Theme.init(); - if (Base.isMacOS()) { - String properMenuBar = "apple.laf.useScreenMenuBar"; - String menubar = Preferences.get(properMenuBar); - if (menubar != null) { - // Get the current menu bar setting and use it - System.setProperty(properMenuBar, menubar); - - } else { - // 10.4 is not affected, 10.5 (and prolly 10.6) are - if (System.getProperty("os.version").startsWith("10.4")) { - // Don't bother checking next time - Preferences.set(properMenuBar, "true"); - // Also set the menubar now - System.setProperty(properMenuBar, "true"); - - } else { - // Running 10.5 or 10.6 or whatever, give 'em the business - String warning = - "" + - " " + - "Some menus have been disabled." + - "

Due to an Apple bug, the Sketchbook and Example menus " + - "are unusable.
" + - "As a workaround, these items will be disabled from the " + - "standard menu bar,
" + - "but you can use the Open button on " + - "the toolbar to access the same items.
" + - "If this bug makes you sad, " + - "please contact Apple via bugreporter.apple.com.

" + - " "; - Object[] options = { "OK", "More Info" }; - int result = JOptionPane.showOptionDialog(new Frame(), - warning, - "Menu Bar Problem", - JOptionPane.YES_NO_OPTION, - JOptionPane.WARNING_MESSAGE, - null, - options, - options[0]); - if (result == -1) { - // They hit ESC or closed the window, so just hide it for now - // But don't bother setting the preference in the file - } else { - // Shut off in the preferences for next time - //Preferences.set(properMenuBar, "false"); - // For 1.0.4, we'll stick with the Apple menu bar, - // and just disable the sketchbook and examples sub-menus. - Preferences.set(properMenuBar, "true"); - if (result == 1) { // More Info - Base.openURL("http://dev.processing.org/bugs/show_bug.cgi?id=786"); - } - } - // Whether or not canceled, set to false (right now) if we're on 10.5 - //System.setProperty(properMenuBar, "false"); - // Changing this behavior for 1.0.4 - System.setProperty(properMenuBar, "true"); - } - } - } - // Set the look and feel before opening the window - // For 0158, moving it lower so that the apple.laf.useScreenMenuBar stuff works try { platform.setLookAndFeel(); } catch (Exception e) { @@ -815,7 +751,8 @@ public class Base { */ public boolean handleClose(Editor editor) { // Check if modified - if (!editor.checkModified(false)) { + boolean immediate = editors.size() == 1; + if (!editor.checkModified(immediate)) { return false; } @@ -993,17 +930,9 @@ public class Base { protected void rebuildSketchbookMenu(JMenu menu) { //System.out.println("rebuilding sketchbook menu"); //new Exception().printStackTrace(); - //boolean nativeButBroken = Base.isMacOS() ? - //Preferences.getBoolean("apple.laf.useScreenMenuBar") : false; - boolean nativeButBroken = false; - try { - if (nativeButBroken) { // osx workaround - menu.setEnabled(false); - } else { menu.removeAll(); addSketches(menu, getSketchbookFolder(), false); - } } catch (IOException e) { e.printStackTrace(); } @@ -1045,21 +974,13 @@ public class Base { public void rebuildExamplesMenu(JMenu menu) { //System.out.println("rebuilding examples menu"); - //boolean nativeButBroken = Base.isMacOS() ? - //Preferences.getBoolean("apple.laf.useScreenMenuBar") : false; - boolean nativeButBroken = false; - try { - if (nativeButBroken) { // osx workaround - menu.setEnabled(false); - } else { - menu.removeAll(); - boolean found = addSketches(menu, examplesFolder, false); - if (found) menu.addSeparator(); - found = addSketches(menu, getSketchbookLibrariesFolder(), false); - if (found) menu.addSeparator(); - addSketches(menu, librariesFolder, false); - } + menu.removeAll(); + boolean found = addSketches(menu, examplesFolder, false); + if (found) menu.addSeparator(); + found = addSketches(menu, getSketchbookLibrariesFolder(), false); + if (found) menu.addSeparator(); + addSketches(menu, librariesFolder, false); } catch (IOException e) { e.printStackTrace(); } @@ -1965,56 +1886,30 @@ public class Base { * Grab the contents of a file as a string. */ static public String loadFile(File file) throws IOException { - return PApplet.join(PApplet.loadStrings(file), "\n"); - - /* - // empty code file.. no worries, might be getting filled up later - if (file.length() == 0) return ""; - - //FileInputStream fis = new FileInputStream(file); - //InputStreamReader isr = new InputStreamReader(fis, "UTF-8"); - //BufferedReader reader = new BufferedReader(isr); - BufferedReader reader = PApplet.createReader(file); - - StringBuffer buffer = new StringBuffer(); - String line = null; - while ((line = reader.readLine()) != null) { -// char[] cc = line.toCharArray(); -// for (int i = 0; i < cc.length; i++) { -// char c = cc[i]; -// if (c < 32 || c > 126) System.out.println("found " + c + " " + ((int) c)); -// } -// - buffer.append(line); - buffer.append('\n'); + String[] contents = PApplet.loadStrings(file); + if (contents == null) return null; + return PApplet.join(contents, "\n"); } - reader.close(); - return buffer.toString(); - */ - } /** * Spew the contents of a String object out to a file. */ static public void saveFile(String str, File file) throws IOException { - PApplet.saveStrings(file, new String[] { str }); - /* - ByteArrayInputStream bis = new ByteArrayInputStream(str.getBytes()); - InputStreamReader isr = new InputStreamReader(bis); - BufferedReader reader = new BufferedReader(isr); - - FileOutputStream fos = new FileOutputStream(file); - OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8"); - PrintWriter writer = new PrintWriter(osw); - - String line = null; - while ((line = reader.readLine()) != null) { - writer.println(line); + File temp = File.createTempFile(file.getName(), null, file.getParentFile()); + PApplet.saveStrings(temp, new String[] { str }); + if (file.exists()) { + boolean result = file.delete(); + if (!result) { + throw new IOException("Could not remove old version of " + + file.getAbsolutePath()); + } + } + boolean result = temp.renameTo(file); + if (!result) { + throw new IOException("Could not replace " + + file.getAbsolutePath()); } - writer.flush(); - writer.close(); - */ } diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index 1775e171d..193444385 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -114,6 +114,9 @@ public class Editor extends JFrame implements RunnerListener { EditorLineStatus lineStatus; + boolean newEditor = true; + JEditorPane editorPane; + JEditTextArea textarea; EditorListener listener; @@ -229,7 +232,22 @@ public class Editor extends JFrame implements RunnerListener { lineStatus = new EditorLineStatus(textarea); consolePanel.add(lineStatus, BorderLayout.SOUTH); +// if (newEditor) { +// try { +// setupEditorPane(); +// upper.add(editorPane); +// } catch (Exception e1) { +// PrintWriter w = PApplet.createWriter(new File("/Users/fry/Desktop/blah.txt")); +// w.println(e1.getMessage()); +// e1.printStackTrace(w); +// w.flush(); +// w.close(); +//// e1.printStackTrace()); +//// e1.printStackTrace(System.out); +// } +// } else { upper.add(textarea); +// } splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, upper, consolePanel); @@ -345,6 +363,46 @@ public class Editor extends JFrame implements RunnerListener { } + /* + // http://wiki.netbeans.org/DevFaqEditorCodeCompletionAnyJEditorPane + void setupEditorPane() throws IOException { + editorPane = new JEditorPane(); + + // This will find the Java editor kit and associate it with + // our editor pane. But that does not give us code completion + // just yet because we have no Java context (i.e. no class path, etc.). + // However, this does give us syntax coloring. + EditorKit kit = CloneableEditorSupport.getEditorKit("text/x-java"); + editorPane.setEditorKit(kit); + + // You can specify any ".java" file. + // If the file does not exist, it will be created. + // The contents of the file does not matter. + // The extension must be ".java", however. +// String newSourcePath = "/Users/fry/Desktop/tmp.java"; + +// File tmpFile = new File(newSourcePath); +// System.out.println(tmpFile.getParent() + " " + tmpFile.getName()); +// FileObject fob = FileUtil.createData(tmpFile); + File tmpFile = File.createTempFile("temp", ".java"); + FileObject fob = FileUtil.toFileObject(FileUtil.normalizeFile(tmpFile)); + + DataObject dob = DataObject.find(fob); + editorPane.getDocument().putProperty(Document.StreamDescriptionProperty, dob); + + // This sets up a default class path for us so that + // we can find all the JDK classes via code completion. + DialogBinding.bindComponentToFile(fob, 0, 0, editorPane); + + // Last but not least, we need to fill the editor pane with + // some initial dummy code - as it seems somehow required to + // kick-start code completion. + // A simple dummy package declaration will do. + editorPane.setText("package dummy;"); + } + */ + + protected void setPlacement(int[] location) { setBounds(location[0], location[1], location[2], location[3]); if (location[4] != 0) { @@ -859,6 +917,13 @@ public class Editor extends JFrame implements RunnerListener { menu.add(createToolMenuItem("processing.app.tools.Archiver")); menu.add(createToolMenuItem("processing.app.tools.FixEncoding")); + /* + //menu.add(createToolMenuItem("processing.app.tools.android.Build")); + item = createToolMenuItem("processing.app.tools.android.Build"); + item.setAccelerator(KeyStroke.getKeyStroke('D', modifiers)); + menu.add(item); + */ + return menu; } diff --git a/app/src/processing/app/Preferences.java b/app/src/processing/app/Preferences.java index 23bb979d8..1bb505373 100644 --- a/app/src/processing/app/Preferences.java +++ b/app/src/processing/app/Preferences.java @@ -34,6 +34,8 @@ import processing.app.syntax.*; import processing.core.*; + + /** * Storage class for user preferences and environment settings. *

@@ -68,13 +70,6 @@ public class Preferences { static final String PREFS_FILE = "preferences.txt"; - // platform strings (used to get settings for specific platforms) - - //static final String platforms[] = { - // "other", "windows", "macosx", "linux" - //}; - - // prompt text stuff static final String PROMPT_YES = "Yes"; @@ -97,12 +92,6 @@ public class Preferences { * inside a static block. */ static public int BUTTON_HEIGHT = 24; - /* - // remove this for 0121, because quaqua takes care of it - static { - if (Base.isMacOS()) BUTTON_HEIGHT = 29; - } - */ // value for the size bars, buttons, etc @@ -125,14 +114,12 @@ public class Preferences { JTextField sketchbookLocationField; JCheckBox exportSeparateBox; JCheckBox deletePreviousBox; -// JCheckBox closingLastQuitsBox; JCheckBox externalEditorBox; JCheckBox memoryOverrideBox; JTextField memoryField; JCheckBox checkUpdatesBox; JTextField fontSizeField; JCheckBox autoAssociateBox; - JCheckBox menubarWorkaroundBox; // the calling editor, so updates can be applied @@ -142,9 +129,9 @@ public class Preferences { // data model - static HashMap defaults; - static HashMap table = new HashMap();; - static HashMap> prefixes = new HashMap>(); + static Hashtable defaults; + static Hashtable table = new Hashtable();; + static Hashtable prefixes = new Hashtable(); static File preferencesFile; @@ -159,8 +146,22 @@ public class Preferences { "You'll need to reinstall Arduino.", e); } + // check for platform-specific properties in the defaults + String platformExt = "." + PConstants.platformNames[PApplet.platform]; + int platformExtLength = platformExt.length(); + Enumeration e = table.keys(); + while (e.hasMoreElements()) { + String key = (String) e.nextElement(); + if (key.endsWith(platformExt)) { + // this is a key specific to a particular platform + String actualKey = key.substring(0, key.length() - platformExtLength); + String value = get(key); + table.put(actualKey, value); + } + } + // clone the hash table - defaults = (HashMap) table.clone(); + defaults = (Hashtable) table.clone(); // other things that have to be set explicitly for the defaults setColor("run.window.bgcolor", SystemColor.control); @@ -197,14 +198,6 @@ public class Preferences { " and restart Arduino.", ex); } } - - // Theme settings always override preferences -// try { -// load(Base.getStream("theme/theme.txt")); -// } catch (Exception te) { -// Base.showError(null, "Could not read color theme settings.\n" + -// "You'll need to reinstall Processing.", te); -// } } try { @@ -250,43 +243,6 @@ public class Preferences { int h, vmax; - // [ ] Quit after closing last sketch window - /* - closingLastQuitsBox = - new JCheckBox("Quit after closing last sketch window"); - pain.add(closingLastQuitsBox); - d = closingLastQuitsBox.getPreferredSize(); - closingLastQuitsBox.setBounds(left, top, d.width + 10, d.height); - right = Math.max(right, left + d.width); - top += d.height + GUI_BETWEEN; - */ - - - /* - // [ ] Prompt for name and folder when creating new sketch - - sketchPromptBox = - new JCheckBox("Prompt for name when opening or creating a sketch"); - pain.add(sketchPromptBox); - d = sketchPromptBox.getPreferredSize(); - sketchPromptBox.setBounds(left, top, d.width + 10, d.height); - right = Math.max(right, left + d.width); - top += d.height + GUI_BETWEEN; - */ - - - // [ ] Delete empty sketches on Quit - - /* - sketchCleanBox = new JCheckBox("Delete empty sketches on Quit"); - pain.add(sketchCleanBox); - d = sketchCleanBox.getPreferredSize(); - sketchCleanBox.setBounds(left, top, d.width + 10, d.height); - right = Math.max(right, left + d.width); - top += d.height + GUI_BETWEEN; - */ - - // Sketchbook location: // [...............................] [ Browse ] @@ -303,17 +259,6 @@ public class Preferences { button = new JButton(PROMPT_BROWSE); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - /* - JFileChooser fc = new JFileChooser(); - fc.setSelectedFile(new File(sketchbookLocationField.getText())); - fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - - int returned = fc.showOpenDialog(new JDialog()); - if (returned == JFileChooser.APPROVE_OPTION) { - File file = fc.getSelectedFile(); - sketchbookLocationField.setText(file.getAbsolutePath()); - } - */ File dflt = new File(sketchbookLocationField.getText()); File file = Base.selectFolder("Select new sketchbook location", dflt, dialog); @@ -327,13 +272,9 @@ public class Preferences { // take max height of all components to vertically align em vmax = Math.max(d.height, d2.height); - //label.setBounds(left, top + (vmax-d.height)/2, - // d.width, d.height); - - //h = left + d.width + GUI_BETWEEN; sketchbookLocationField.setBounds(left, top + (vmax-d.height)/2, d.width, d.height); - h = left + d.width + GUI_SMALL; //GUI_BETWEEN; + h = left + d.width + GUI_SMALL; button.setBounds(h, top + (vmax-d2.height)/2, d2.width, d2.height); @@ -402,22 +343,6 @@ public class Preferences { } - // [ ] Place menu bar inside - - if (Base.isMacOS()) { - if (System.getProperty("os.version").startsWith("10.5")) { - menubarWorkaroundBox = - new JCheckBox("Place menus inside editor window to avoid " + - "Apple Java bug (requires restart)"); - pain.add(menubarWorkaroundBox); - d = menubarWorkaroundBox.getPreferredSize(); - menubarWorkaroundBox.setBounds(left, top, d.width + 10, d.height); - right = Math.max(right, left + d.width); - top += d.height + GUI_BETWEEN; - } - } - - // More preferences are in the ... label = new JLabel("More preferences can be edited directly in the file"); @@ -491,7 +416,6 @@ public class Preferences { wide = right + GUI_BIG; high = top + GUI_SMALL; - //setSize(wide, high); // closing the window is same as hitting cancel button @@ -535,26 +459,6 @@ public class Preferences { } - /* - protected JRootPane createRootPane() { - System.out.println("creating root pane esc received"); - - ActionListener actionListener = new ActionListener() { - public void actionPerformed(ActionEvent actionEvent) { - //setVisible(false); - System.out.println("esc received"); - } - }; - - JRootPane rootPane = new JRootPane(); - KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); - rootPane.registerKeyboardAction(actionListener, stroke, - JComponent.WHEN_IN_FOCUSED_WINDOW); - return rootPane; - } - */ - - public Dimension getPreferredSize() { return new Dimension(wide, high); } @@ -624,11 +528,6 @@ public class Preferences { autoAssociateBox.isSelected()); } - if (menubarWorkaroundBox != null) { - setBoolean("apple.laf.useScreenMenuBar", - !menubarWorkaroundBox.isSelected()); - } - editor.applyPreferences(); } @@ -659,11 +558,6 @@ public class Preferences { setSelected(getBoolean("platform.auto_file_type_associations")); } - if (menubarWorkaroundBox != null) { - menubarWorkaroundBox. - setSelected(!getBoolean("apple.laf.useScreenMenuBar")); - } - dialog.setVisible(true); } @@ -676,16 +570,12 @@ public class Preferences { } static protected void load(InputStream input, String prefix) throws IOException { - LinkedHashMap table = new LinkedHashMap(); + LinkedHashMap table = new LinkedHashMap(); prefixes.put(prefix, table); load(input, table); } static protected void load(InputStream input, Map table) throws IOException { - // check for platform-specific properties in the defaults - String platformExt = "." + PConstants.platformNames[PApplet.platform]; - int platformExtLength = platformExt.length(); - String[] lines = PApplet.loadStrings(input); // Reads as UTF-8 for (String line : lines) { if ((line.length() == 0) || @@ -695,12 +585,6 @@ public class Preferences { int equals = line.indexOf('='); if (equals != -1) { String key = line.substring(0, equals).trim(); - - // check if this is a platform-specific key, and if so, shave things - if (key.endsWith(platformExt)) { - // this is a key specific to this platform - key = key.substring(0, key.length() - platformExtLength); - } String value = line.substring(equals + 1).trim(); table.put(key, value); } @@ -721,8 +605,10 @@ public class Preferences { // Fix for 0163 to properly use Unicode when writing preferences.txt PrintWriter writer = PApplet.createWriter(preferencesFile); - for (String key : table.keySet()) { - writer.println(key + "=" + table.get(key)); + Enumeration e = table.keys(); //properties.propertyNames(); + while (e.hasMoreElements()) { + String key = (String) e.nextElement(); + writer.println(key + "=" + ((String) table.get(key))); } writer.flush(); @@ -748,15 +634,15 @@ public class Preferences { // preference files, look up the attribute in that file's Hashtable // (don't override with or fallback to the main file). otherwise, // look up the attribute in the main file's Hashtable. - HashMap table = Preferences.table; + Map table = Preferences.table; if (attribute.indexOf('.') != -1) { String prefix = attribute.substring(0, attribute.indexOf('.')); if (prefixes.containsKey(prefix)) { - table = prefixes.get(prefix); + table = (Map) prefixes.get(prefix); attribute = attribute.substring(attribute.indexOf('.') + 1); } } - return table.get(attribute); + return (String) table.get(attribute); /* //String value = (properties != null) ? //properties.getProperty(attribute) : applet.getParameter(attribute); @@ -791,7 +677,7 @@ public class Preferences { static public String getDefault(String attribute) { - return defaults.get(attribute); + return (String) defaults.get(attribute); } diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index d56b127cf..042680018 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -233,11 +233,12 @@ public class Sketch { ensureExistence(); // add file to the code/codeCount list, resort the list - if (codeCount == code.length) { + //if (codeCount == code.length) { code = (SketchCode[]) PApplet.append(code, newCode); + codeCount++; + //} + //code[codeCount++] = newCode; } - code[codeCount++] = newCode; - } protected void sortCode() { @@ -390,7 +391,7 @@ public class Sketch { // Make sure no .pde *and* no .java files with the same name already exist // http://dev.processing.org/bugs/show_bug.cgi?id=543 for (SketchCode c : code) { - if (sanitaryName.equals(c.getPrettyName())) { + if (sanitaryName.equalsIgnoreCase(c.getPrettyName())) { Base.showMessage("Nope", "A file named \"" + c.getFileName() + "\" already exists\n" + "in \"" + folder.getAbsolutePath() + "\""); @@ -599,6 +600,7 @@ public class Sketch { code[j] = code[j+1]; } codeCount--; + code = (SketchCode[]) PApplet.shorten(code); return; } } @@ -759,7 +761,7 @@ public class Sketch { // but ignore this situation for the first tab, since it's probably being // resaved (with the same name) to another location/folder. for (int i = 1; i < codeCount; i++) { - if (newName.equals(code[i].getPrettyName())) { + if (newName.equalsIgnoreCase(code[i].getPrettyName())) { Base.showMessage("Nope", "You can't save the sketch as \"" + newName + "\"\n" + "because the sketch already has a tab with that name."); @@ -1198,6 +1200,10 @@ public class Sketch { * @return null if compilation failed, main class name if not */ public String preprocess(String buildPath, Target target) throws RunnerException { + return preprocess(buildPath, new PdePreprocessor(), target); + } + + public String preprocess(String buildPath, PdePreprocessor preprocessor, Target target) throws RunnerException { // make sure the user didn't hide the sketch folder ensureExistence(); @@ -1268,7 +1274,18 @@ public class Sketch { // Note that the headerOffset isn't applied until compile and run, because // it only applies to the code after it's been written to the .java file. int headerOffset = 0; - PdePreprocessor preprocessor = new PdePreprocessor(); + //PdePreprocessor preprocessor = new PdePreprocessor(); + try { + headerOffset = preprocessor.writePrefix(bigCode.toString(), + buildPath, + name, + codeFolderPackages, + target); + } catch (FileNotFoundException fnfe) { + fnfe.printStackTrace(); + String msg = "Build folder disappeared or could not be written"; + throw new RunnerException(msg); + } // 2. run preproc on that code using the sugg class name // to create a single .java file and write to buildpath @@ -1278,12 +1295,7 @@ public class Sketch { try { // if (i != 0) preproc will fail if a pde file is not // java mode, since that's required - String className = preprocessor.write(bigCode.toString(), - buildPath, - name, - codeFolderPackages, - target); - headerOffset = preprocessor.headerCount + preprocessor.prototypeCount; + String className = preprocessor.write(); if (className == null) { throw new RunnerException("Could not find main class"); diff --git a/app/src/processing/app/debug/Runner.java b/app/src/processing/app/debug/Runner.java index b78ee41c3..f439eeb98 100644 --- a/app/src/processing/app/debug/Runner.java +++ b/app/src/processing/app/debug/Runner.java @@ -385,9 +385,15 @@ public class Runner implements MessageConsumer { commandArgs = "java -Xrunjdwp:transport=dt_shmem,address=" + addr + ",suspend=y "; } else if (Base.isMacOS()) { + if (System.getProperty("os.version").startsWith("10.4")) { + // -d32 not understood by 10.4 (and not needed) commandArgs = + "java -Xrunjdwp:transport=dt_socket,address=" + addr + ",suspend=y "; + } else { + commandArgs = "java -d32 -Xrunjdwp:transport=dt_socket,address=" + addr + ",suspend=y "; } + } for (int i = 0; i < vmParams.length; i++) { commandArgs = addArgument(commandArgs, vmParams[i], ' '); diff --git a/app/src/processing/app/macosx/Platform.java b/app/src/processing/app/macosx/Platform.java index a7180aadc..6a156e8f0 100644 --- a/app/src/processing/app/macosx/Platform.java +++ b/app/src/processing/app/macosx/Platform.java @@ -48,6 +48,7 @@ public class Platform extends processing.app.Platform { public void init(Base base) { + System.setProperty("apple.laf.useScreenMenuBar", "true"); ThinkDifferent.init(base); /* try { @@ -129,6 +130,7 @@ public class Platform extends processing.app.Platform { } */ } + // for Java 1.6, replace with java.awt.Desktop.browse() and java.awt.Desktop.open() com.apple.eio.FileManager.openURL(url); } diff --git a/app/src/processing/app/preproc/PdePreprocessor.java b/app/src/processing/app/preproc/PdePreprocessor.java index e0ba89681..c2e38cd50 100644 --- a/app/src/processing/app/preproc/PdePreprocessor.java +++ b/app/src/processing/app/preproc/PdePreprocessor.java @@ -34,58 +34,222 @@ import processing.app.debug.Target; import processing.core.*; import java.io.*; -import java.util.ArrayList; -import java.util.List; - -import antlr.*; -import antlr.collections.*; -import antlr.collections.impl.*; +import java.util.*; import com.oroinc.text.regex.*; +/** + * Class that orchestrates preprocessing p5 syntax into straight Java. + */ public class PdePreprocessor { - - static final int JDK11 = 0; - static final int JDK13 = 1; - static final int JDK14 = 2; - - //static String defaultImports[][] = new String[3][]; - - // these ones have the .* at the end, since a class name - // might be at the end instead of .* whcih would make trouble - // other classes using this can lop of the . and anything after - // it to produce a package name consistently. - //public String extraImports[]; - ArrayList programImports; - - static public final int STATIC = 0; // formerly BEGINNER - static public final int ACTIVE = 1; // formerly INTERMEDIATE - static public final int JAVA = 2; // formerly ADVANCED - // static to make it easier for the antlr preproc to get at it - static public int programType = -1; - - Reader programReader; - String buildPath; - // stores number of built user-defined function prototypes public int prototypeCount = 0; // stores number of included library headers written // we always write one header: WProgram.h public int headerCount = 1; - - /** - * These may change in-between (if the prefs panel adds this option) - * so grab them here on construction. - */ - public PdePreprocessor() {} - - /** - * Used by PdeEmitter.dumpHiddenTokens() - */ - //public static TokenStreamCopyingHiddenTokenFilter filter; + Target target; + List prototypes; + + + + + String[] defaultImports; + + // these ones have the .* at the end, since a class name might be at the end + // instead of .* which would make trouble other classes using this can lop + // off the . and anything after it to produce a package name consistently. + //public String extraImports[]; + ArrayList programImports; + + // imports just from the code folder, treated differently + // than the others, since the imports are auto-generated. + ArrayList codeFolderImports; + + String indent; + + PrintStream stream; + String program; + String buildPath; + String name; + + + /** + * Setup a new preprocessor. + */ + public PdePreprocessor() { } + + public int writePrefix(String program, String buildPath, + String name, String codeFolderPackages[], + Target target) + throws FileNotFoundException { + this.buildPath = buildPath; + this.name = name; + this.target = target; + + int tabSize = Preferences.getInteger("editor.tabs.size"); + char[] indentChars = new char[tabSize]; + Arrays.fill(indentChars, ' '); + indent = new String(indentChars); + + // if the program ends with no CR or LF an OutOfMemoryError will happen. + // not gonna track down the bug now, so here's a hack for it: + // http://dev.processing.org/bugs/show_bug.cgi?id=5 + program += "\n"; + + // if the program ends with an unterminated multi-line comment, + // an OutOfMemoryError or NullPointerException will happen. + // again, not gonna bother tracking this down, but here's a hack. + // http://dev.processing.org/bugs/show_bug.cgi?id=16 + Sketch.scrubComments(program); + // this returns the scrubbed version, but more important for this + // function, it'll check to see if there are errors with the comments. + + if (Preferences.getBoolean("preproc.substitute_unicode")) { + // check for non-ascii chars (these will be/must be in unicode format) + char p[] = program.toCharArray(); + int unicodeCount = 0; + for (int i = 0; i < p.length; i++) { + if (p[i] > 127) unicodeCount++; + } + // if non-ascii chars are in there, convert to unicode escapes + if (unicodeCount != 0) { + // add unicodeCount * 5.. replacing each unicode char + // with six digit uXXXX sequence (xxxx is in hex) + // (except for nbsp chars which will be a replaced with a space) + int index = 0; + char p2[] = new char[p.length + unicodeCount*5]; + for (int i = 0; i < p.length; i++) { + if (p[i] < 128) { + p2[index++] = p[i]; + + } else if (p[i] == 160) { // unicode for non-breaking space + p2[index++] = ' '; + + } else { + int c = p[i]; + p2[index++] = '\\'; + p2[index++] = 'u'; + char str[] = Integer.toHexString(c).toCharArray(); + // add leading zeros, so that the length is 4 + //for (int i = 0; i < 4 - str.length; i++) p2[index++] = '0'; + for (int m = 0; m < 4 - str.length; m++) p2[index++] = '0'; + System.arraycopy(str, 0, p2, index, str.length); + index += str.length; + } + } + program = new String(p2, 0, index); + } + } + + // These may change in-between (if the prefs panel adds this option) + // so grab them here on construction. + String prefsLine = Preferences.get("preproc.imports"); + defaultImports = PApplet.splitTokens(prefsLine, ", "); + + //String importRegexp = "(?:^|\\s|;)(import\\s+)(\\S+)(\\s*;)"; + String importRegexp = "^\\s*#include\\s+[<\"](\\S+)[\">]"; + programImports = new ArrayList(); + + String[][] pieces = PApplet.matchAll(program, importRegexp); + + if (pieces != null) + for (int i = 0; i < pieces.length; i++) + programImports.add(pieces[i][1]); // the package name + + codeFolderImports = new ArrayList(); +// if (codeFolderPackages != null) { +// for (String item : codeFolderPackages) { +// codeFolderImports.add(item + ".*"); +// } +// } + + prototypes = new ArrayList(); + + try { + prototypes = prototypes(program); + } catch (MalformedPatternException e) { + System.out.println("Internal error while pre-processing; " + + "not generating function prototypes.\n\n" + e); + } + + // store # of prototypes so that line number reporting can be adjusted + prototypeCount = prototypes.size(); + + // do this after the program gets re-combobulated + this.program = program; + + // output the code + File streamFile = new File(buildPath, name + ".cpp"); + stream = new PrintStream(new FileOutputStream(streamFile)); + + return headerCount + prototypeCount; + } + + /** + * preprocesses a pde file and write out a java file + * @return the classname of the exported Java + */ + //public String write(String program, String buildPath, String name, + // String extraImports[]) throws java.lang.Exception { + public String write() throws java.lang.Exception { + writeProgram(stream, program, prototypes); + writeFooter(stream, target); + stream.close(); + + return name; + } + + // Write the pde program to the cpp file + protected void writeProgram(PrintStream out, String program, List prototypes) { + int prototypeInsertionPoint = firstStatement(program); + + out.print(program.substring(0, prototypeInsertionPoint)); + out.print("#include \"WProgram.h\"\n"); + + // print user defined prototypes + for (int i = 0; i < prototypes.size(); i++) { + out.print(prototypes.get(i) + "\n"); + } + + out.print(program.substring(prototypeInsertionPoint)); + } + + + /** + * Write any necessary closing text. + * + * @param out PrintStream to write it to. + */ + protected void writeFooter(PrintStream out, Target target) throws java.lang.Exception { + // Open the file main.cxx and copy its entire contents to the bottom of the + // generated sketch .cpp file... + + String mainFileName = target.getPath() + File.separator + "main.cxx"; + FileReader reader = null; + reader = new FileReader(mainFileName); + + LineNumberReader mainfile = new LineNumberReader(reader); + + String line; + while ((line = mainfile.readLine()) != null) { + out.print(line + "\n"); + } + + mainfile.close(); + } + + + public ArrayList getExtraImports() { + return programImports; + } + + + + + /** * Returns the index of the first character that's not whitespace, a comment * or a pre-processor directive. @@ -204,185 +368,4 @@ public class PdePreprocessor { return matches; } - - - /** - * preprocesses a pde file and write out a java file - * @param pretty true if should also space out/indent lines - * @return the classname of the exported Java - */ - //public String write(String program, String buildPath, String name, - // String extraImports[]) throws java.lang.Exception { - public String write(String program, String buildPath, - String name, String codeFolderPackages[], - Target target) - throws java.lang.Exception { - // if the program ends with no CR or LF an OutOfMemoryError will happen. - // not gonna track down the bug now, so here's a hack for it: - // bug filed at http://dev.processing.org/bugs/show_bug.cgi?id=5 - //if ((program.length() > 0) && - //program.charAt(program.length()-1) != '\n') { - program += "\n"; - //} - - // if the program ends with an unterminated multiline comment, - // an OutOfMemoryError or NullPointerException will happen. - // again, not gonna bother tracking this down, but here's a hack. - // http://dev.processing.org/bugs/show_bug.cgi?id=16 - Sketch.scrubComments(program); - // this returns the scrubbed version, but more important for this - // function, it'll check to see if there are errors with the comments. - - if (Preferences.getBoolean("preproc.substitute_unicode")) { - // check for non-ascii chars (these will be/must be in unicode format) - char p[] = program.toCharArray(); - int unicodeCount = 0; - for (int i = 0; i < p.length; i++) { - if (p[i] > 127) unicodeCount++; - } - // if non-ascii chars are in there, convert to unicode escapes - if (unicodeCount != 0) { - // add unicodeCount * 5.. replacing each unicode char - // with six digit uXXXX sequence (xxxx is in hex) - // (except for nbsp chars which will be a replaced with a space) - int index = 0; - char p2[] = new char[p.length + unicodeCount*5]; - for (int i = 0; i < p.length; i++) { - if (p[i] < 128) { - p2[index++] = p[i]; - - } else if (p[i] == 160) { // unicode for non-breaking space - p2[index++] = ' '; - - } else { - int c = p[i]; - p2[index++] = '\\'; - p2[index++] = 'u'; - char str[] = Integer.toHexString(c).toCharArray(); - // add leading zeros, so that the length is 4 - //for (int i = 0; i < 4 - str.length; i++) p2[index++] = '0'; - for (int m = 0; m < 4 - str.length; m++) p2[index++] = '0'; - System.arraycopy(str, 0, p2, index, str.length); - index += str.length; - } - } - program = new String(p2, 0, index); - } - } - - // if this guy has his own imports, need to remove them - // just in case it's not an advanced mode sketch - PatternMatcher matcher = new Perl5Matcher(); - PatternCompiler compiler = new Perl5Compiler(); - //String mess = "^\\s*(import\\s+\\S+\\s*;)"; - //String mess = "^\\s*(import\\s+)(\\S+)(\\s*;)"; - String mess = "^\\s*#include\\s+[<\"](\\S+)[\">]"; - programImports = new ArrayList(); - - Pattern pattern = null; - try { - pattern = compiler.compile(mess); - } catch (MalformedPatternException e) { - e.printStackTrace(); - return null; - } - - PatternMatcherInput input = new PatternMatcherInput(program); - while (matcher.contains(input, pattern)) { - programImports.add(matcher.getMatch().group(1)); - } - - // do this after the program gets re-combobulated - this.programReader = new StringReader(program); - this.buildPath = buildPath; - - List prototypes = prototypes(program); - - // store # of prototypes so that line number reporting can be adjusted - prototypeCount = prototypes.size(); - - if (name == null) return null; - - // output the code - File streamFile = new File(buildPath, name + ".cpp"); - PrintStream stream = new PrintStream(new FileOutputStream(streamFile)); - - writeHeader(stream); - //added to write the pde code to the cpp file - writeProgram(stream, program, prototypes); - writeFooter(stream, target); - stream.close(); - - return name; - } - - // Write the pde program to the cpp file - void writeProgram(PrintStream out, String program, List prototypes) { - int prototypeInsertionPoint = firstStatement(program); - - out.print(program.substring(0, prototypeInsertionPoint)); - out.print("#include \"WProgram.h\"\n"); - - // print user defined prototypes - for (int i = 0; i < prototypes.size(); i++) { - out.print(prototypes.get(i) + "\n"); - } - - out.print(program.substring(prototypeInsertionPoint)); - } - - - /** - * Write any required header material (eg imports, class decl stuff) - * - * @param out PrintStream to write it to. - */ - void writeHeader(PrintStream out) throws IOException {} - - /** - * Write any necessary closing text. - * - * @param out PrintStream to write it to. - */ - void writeFooter(PrintStream out, Target target) throws java.lang.Exception { - // Open the file main.cxx and copy its entire contents to the bottom of the - // generated sketch .cpp file... - - String mainFileName = target.getPath() + File.separator + "main.cxx"; - FileReader reader = null; - reader = new FileReader(mainFileName); - - LineNumberReader mainfile = new LineNumberReader(reader); - - String line; - while ((line = mainfile.readLine()) != null) { - out.print(line + "\n"); - } - - mainfile.close(); - } - - - public ArrayList getExtraImports() { - return programImports; - } - - - static String advClassName = ""; - - /** - * Find the first CLASS_DEF node in the tree, and return the name of the - * class in question. - * - * XXXdmose right now, we're using a little hack to the grammar to get - * this info. In fact, we should be descending the AST passed in. - */ - String getFirstClassName(AST ast) { - - String t = advClassName; - advClassName = ""; - - return t; - } - } diff --git a/build/macosx/dist/Arduino.app/Contents/Info.plist b/build/macosx/dist/Arduino.app/Contents/Info.plist index 0f6a7cff2..9cabd308e 100755 --- a/build/macosx/dist/Arduino.app/Contents/Info.plist +++ b/build/macosx/dist/Arduino.app/Contents/Info.plist @@ -48,7 +48,10 @@ Java VMOptions - -Xms128M -Xmx256M + + -Xms128M + -Xmx256M + MainClass processing.app.Base @@ -62,26 +65,23 @@ http://dev.processing.org/bugs/show_bug.cgi?id=1045 --> $JAVAROOT/pde.jar:$JAVAROOT/core.jar:$JAVAROOT/antlr.jar:$JAVAROOT/ecj.jar:$JAVAROOT/registry.jar:$JAVAROOT/quaqua.jar:$JAVAROOT/oro.jar:$JAVAROOT/RXTXcomm.jar + JVMArchs + + + i386 + ppc + + + Properties javaroot $JAVAROOT - - - - - JVMArchs - - - i386 - ppc - + + apple.laf.useScreenMenuBar + true apple.awt.showGrowBox false diff --git a/build/macosx/make.sh b/build/macosx/make.sh index e53091bc5..6b247d839 100755 --- a/build/macosx/make.sh +++ b/build/macosx/make.sh @@ -9,12 +9,6 @@ then echo "or modify this script to remove use of the -X switch to continue." # and you will also need to remove this error message exit -else - if [ "$OSX_VERSION" != "10.6" ] - then - echo "Note: This script has not been tested on this " - echo "release of Mac OS and may cause errors." - fi fi diff --git a/build/shared/lib/preferences.txt b/build/shared/lib/preferences.txt index a9d3c1deb..d1c7b644f 100755 --- a/build/shared/lib/preferences.txt +++ b/build/shared/lib/preferences.txt @@ -147,7 +147,6 @@ run.display = 1 #sketchbook.closing_last_window_quits = true #sketchbook.closing_last_window_quits.macosx = false -apple.laf.useScreenMenuBar=true # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! diff --git a/build/shared/libraries/howto.txt b/build/shared/libraries/howto.txt deleted file mode 100644 index 9865ab19a..000000000 --- a/build/shared/libraries/howto.txt +++ /dev/null @@ -1,5 +0,0 @@ -This file has been replaced by an entire section of dev.processing.org -dedicated to libraries, people who make libraries, and people who -love library development trivia. The new page can be found here: - -http://dev.processing.org/libraries/ diff --git a/build/shared/libraries/javascript/library/export.txt b/build/shared/libraries/javascript/library/export.txt deleted file mode 100644 index a642bb596..000000000 --- a/build/shared/libraries/javascript/library/export.txt +++ /dev/null @@ -1,6 +0,0 @@ -# don't actually export anything.. this is only to link against -# inside of the p5 environment -applet= - -# for an application, export to prevent from breaking -application=javascript.jar \ No newline at end of file diff --git a/build/shared/libraries/javascript/library/javascript.jar b/build/shared/libraries/javascript/library/javascript.jar deleted file mode 100644 index 93c7b9251..000000000 Binary files a/build/shared/libraries/javascript/library/javascript.jar and /dev/null differ diff --git a/build/shared/libraries/minim/library/jl1.0.jar b/build/shared/libraries/minim/library/jl1.0.jar deleted file mode 100755 index 17f7c0aa7..000000000 Binary files a/build/shared/libraries/minim/library/jl1.0.jar and /dev/null differ diff --git a/build/shared/libraries/minim/library/jsminim.jar b/build/shared/libraries/minim/library/jsminim.jar deleted file mode 100755 index 4e6f6e564..000000000 Binary files a/build/shared/libraries/minim/library/jsminim.jar and /dev/null differ diff --git a/build/shared/libraries/minim/library/minim-spi.jar b/build/shared/libraries/minim/library/minim-spi.jar deleted file mode 100755 index 4760de485..000000000 Binary files a/build/shared/libraries/minim/library/minim-spi.jar and /dev/null differ diff --git a/build/shared/libraries/minim/library/minim.jar b/build/shared/libraries/minim/library/minim.jar deleted file mode 100755 index eb053ccec..000000000 Binary files a/build/shared/libraries/minim/library/minim.jar and /dev/null differ diff --git a/build/shared/libraries/minim/library/mp3spi1.9.4.jar b/build/shared/libraries/minim/library/mp3spi1.9.4.jar deleted file mode 100755 index 019b86c93..000000000 Binary files a/build/shared/libraries/minim/library/mp3spi1.9.4.jar and /dev/null differ diff --git a/build/shared/libraries/minim/library/tritonus_aos.jar b/build/shared/libraries/minim/library/tritonus_aos.jar deleted file mode 100755 index 4a02386d4..000000000 Binary files a/build/shared/libraries/minim/library/tritonus_aos.jar and /dev/null differ diff --git a/build/shared/libraries/minim/library/tritonus_share.jar b/build/shared/libraries/minim/library/tritonus_share.jar deleted file mode 100755 index bb367d17e..000000000 Binary files a/build/shared/libraries/minim/library/tritonus_share.jar and /dev/null differ diff --git a/build/shared/libraries/minim/license.txt b/build/shared/libraries/minim/license.txt deleted file mode 100755 index d511905c1..000000000 --- a/build/shared/libraries/minim/license.txt +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program 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 General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/build/shared/libraries/minim/version.txt b/build/shared/libraries/minim/version.txt deleted file mode 100755 index 10bf840ed..000000000 --- a/build/shared/libraries/minim/version.txt +++ /dev/null @@ -1 +0,0 @@ -2.0.1 \ No newline at end of file diff --git a/build/shared/revisions.txt b/build/shared/revisions.txt index 71599c041..9bd545c72 100644 --- a/build/shared/revisions.txt +++ b/build/shared/revisions.txt @@ -1,3 +1,92 @@ +PROCESSING 1.0.9 (REV 0171) - 20 October 2009 + ++ Removed NPOT texture support until further testing, because it was + resulting in blurring images in OPENGL sketches. + http://dev.processing.org/bugs/show_bug.cgi?id=1352 + ++ Complete the excision of the Apple menu bug code. + http://dev.processing.org/bugs/show_bug.cgi?id=786 + + +. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + +PROCESSING 1.0.8 (REV 0170) - 18 October 2009 + +A bonfire of bug fixes. + +[ environment ] + ++ Fix bug causing preferences to not save correctly. + http://dev.processing.org/bugs/show_bug.cgi?id=1320 + http://dev.processing.org/bugs/show_bug.cgi?id=1322 + http://dev.processing.org/bugs/show_bug.cgi?id=1325 + http://dev.processing.org/bugs/show_bug.cgi?id=1329 + http://dev.processing.org/bugs/show_bug.cgi?id=1336 + http://dev.processing.org/bugs/show_bug.cgi?id=1337 + http://dev.processing.org/bugs/show_bug.cgi?id=1344 + ++ Remove menu dimming code, in-frame menu bar, and warning message on OS X. + A year later, Apple fixed the spinning wheel w/ the menu bar problem. + http://dev.processing.org/bugs/show_bug.cgi?id=786 + ++ Fix "Unrecognized option: -d32" on OS X 10.4 + http://dev.processing.org/bugs/show_bug.cgi?id=1324 + ++ Update the outdated "Get the latest Java Plug-in here" in exported applets. + http://dev.processing.org/bugs/show_bug.cgi?id=1331 + ++ Use temporary files when saving files inside the PDE. Prevents problems + when the save goes badly (e.g. disk is full). + http://dev.processing.org/bugs/show_bug.cgi?id=967 + ++ Fix problem with "Save changes before closing?" was being ignored. + http://dev.processing.org/bugs/show_bug.cgi?id=1193 + ++ Fix problems with adding/deleting tabs. + http://dev.processing.org/bugs/show_bug.cgi?id=1332 + http://dev.processing.org/bugs/show_bug.cgi?id=1092 + ++ Saving the project with the same name (but different case) + as an existing tab was deleting code on Windows and OS X. + http://dev.processing.org/bugs/show_bug.cgi?id=1102 + +[ core ] + ++ filter(RGB) supposed to be filter(OPAQUE) + http://dev.processing.org/bugs/show_bug.cgi?id=1346 + ++ Implement non-power-of-2 textures for OpenGL (on cards where available). + This is a partial fix for texture edge problems: + http://dev.processing.org/bugs/show_bug.cgi?id=602 + ++ Fix get() when used with save() in OpenGL mode + ++ Immediately update projection with OpenGL. In the past, projection + updates required a new frame. This also prevents camera/project from + being reset when the drawing size is changed. + ++ Removed an error that caused the cameraNear value to be set to -8. + This may cause other problems with drawing/clipping however. + ++ Removed methods from PApplet that use doubles. These were only temporarily + available in SVN, but that's that. + ++ Use temporary file with saveStrings(File) and saveBytes(File). + +[ updates ] + ++ Updated to Minim 2.0.2. (Thanks Damien!) + http://code.compartmental.net/tools/minim + ++ Updated Java on Linux and Windows to 6u16. + ++ Updated Quaqua to 6.2 on Mac OS X. + + +. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + PROCESSING 1.0.7 (REV 0169) - 4 September 2009 Bug fixes and updates, also some tweaks for Mac OS X Snow Leopard. diff --git a/core/done.txt b/core/done.txt index caf3ddeb2..6147f40ca 100644 --- a/core/done.txt +++ b/core/done.txt @@ -1,3 +1,29 @@ +0170 core (1.0.8) +X added some min/max functions that work with doubles +X not sure if those are staying in or not +X filter(RGB) supposed to be filter(OPAQUE) +X http://dev.processing.org/bugs/show_bug.cgi?id=1346 +X implement non-power-of-2 textures +X fix get() when used with save() in OpenGL mode +X immediately update projection with OpenGL +X in the past, projection updates required a new frame +X also prevents camera/project from being reset with setSize() (regression?) +X which was a problem anyway because it made GL calls outside draw() +X partial fix for texture edge problems with opengl +o http://dev.processing.org/bugs/show_bug.cgi?id=602 +X some camera problems may be coming from "cameraNear = -8" line +X this may cause other problems with drawing/clipping however +X opengl camera does not update on current frame (has to do a second frame) +X remove methods from PApplet that use doubles + + +0169 core (1.0.7) +X remove major try/catch block from PApplet.main() +X hopefully will allow some exception stuff to come through properly +X PVector.angleDistance() returns NaN +X http://dev.processing.org/bugs/show_bug.cgi?id=1316 + + 0168 core (1.0.6) X getImage() setting the wrong image type X http://dev.processing.org/bugs/show_bug.cgi?id=1282 diff --git a/core/preproc/.classpath b/core/preproc/.classpath new file mode 100644 index 000000000..d9132e9f4 --- /dev/null +++ b/core/preproc/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/core/preproc/.project b/core/preproc/.project new file mode 100644 index 000000000..629f1c16a --- /dev/null +++ b/core/preproc/.project @@ -0,0 +1,17 @@ + + + preproc + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/core/preproc/build.xml b/core/preproc/build.xml new file mode 100644 index 000000000..b67757097 --- /dev/null +++ b/core/preproc/build.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/preproc/demo/PApplet.java b/core/preproc/demo/PApplet.java new file mode 100644 index 000000000..950a89d4c --- /dev/null +++ b/core/preproc/demo/PApplet.java @@ -0,0 +1,8155 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2004-09 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.core; + +import java.applet.*; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.*; +import java.io.*; +import java.lang.reflect.*; +import java.net.*; +import java.text.*; +import java.util.*; +import java.util.regex.*; +import java.util.zip.*; + +import javax.imageio.ImageIO; +import javax.swing.JFileChooser; +import javax.swing.SwingUtilities; + + +/** + * Base class for all sketches that use processing.core. + *

+ * Note that you should not use AWT or Swing components inside a Processing + * applet. The surface is made to automatically update itself, and will cause + * problems with redraw of components drawn above it. If you'd like to + * integrate other Java components, see below. + *

+ * As of release 0145, Processing uses active mode rendering in all cases. + * All animation tasks happen on the "Processing Animation Thread". The + * setup() and draw() methods are handled by that thread, and events (like + * mouse movement and key presses, which are fired by the event dispatch + * thread or EDT) are queued to be (safely) handled at the end of draw(). + * For code that needs to run on the EDT, use SwingUtilities.invokeLater(). + * When doing so, be careful to synchronize between that code (since + * invokeLater() will make your code run from the EDT) and the Processing + * animation thread. Use of a callback function or the registerXxx() methods + * in PApplet can help ensure that your code doesn't do something naughty. + *

+ * As of release 0136 of Processing, we have discontinued support for versions + * of Java prior to 1.5. We don't have enough people to support it, and for a + * project of our size, we should be focusing on the future, rather than + * working around legacy Java code. In addition, Java 1.5 gives us access to + * better timing facilities which will improve the steadiness of animation. + *

+ * This class extends Applet instead of JApplet because 1) historically, + * we supported Java 1.1, which does not include Swing (without an + * additional, sizable, download), and 2) Swing is a bloated piece of crap. + * A Processing applet is a heavyweight AWT component, and can be used the + * same as any other AWT component, with or without Swing. + *

+ * Similarly, Processing runs in a Frame and not a JFrame. However, there's + * nothing to prevent you from embedding a PApplet into a JFrame, it's just + * that the base version uses a regular AWT frame because there's simply + * no need for swing in that context. If people want to use Swing, they can + * embed themselves as they wish. + *

+ * It is possible to use PApplet, along with core.jar in other projects. + * In addition to enabling you to use Java 1.5+ features with your sketch, + * this also allows you to embed a Processing drawing area into another Java + * application. This means you can use standard GUI controls with a Processing + * sketch. Because AWT and Swing GUI components cannot be used on top of a + * PApplet, you can instead embed the PApplet inside another GUI the way you + * would any other Component. + *

+ * It is also possible to resize the Processing window by including + * frame.setResizable(true) inside your setup() method. + * Note that the Java method frame.setSize() will not work unless + * you first set the frame to be resizable. + *

+ * Because the default animation thread will run at 60 frames per second, + * an embedded PApplet can make the parent sluggish. You can use frameRate() + * to make it update less often, or you can use noLoop() and loop() to disable + * and then re-enable looping. If you want to only update the sketch + * intermittently, use noLoop() inside setup(), and redraw() whenever + * the screen needs to be updated once (or loop() to re-enable the animation + * thread). The following example embeds a sketch and also uses the noLoop() + * and redraw() methods. You need not use noLoop() and redraw() when embedding + * if you want your application to animate continuously. + *

+ * public class ExampleFrame extends Frame {
+ *
+ *     public ExampleFrame() {
+ *         super("Embedded PApplet");
+ *
+ *         setLayout(new BorderLayout());
+ *         PApplet embed = new Embedded();
+ *         add(embed, BorderLayout.CENTER);
+ *
+ *         // important to call this whenever embedding a PApplet.
+ *         // It ensures that the animation thread is started and
+ *         // that other internal variables are properly set.
+ *         embed.init();
+ *     }
+ * }
+ *
+ * public class Embedded extends PApplet {
+ *
+ *     public void setup() {
+ *         // original setup code here ...
+ *         size(400, 400);
+ *
+ *         // prevent thread from starving everything else
+ *         noLoop();
+ *     }
+ *
+ *     public void draw() {
+ *         // drawing code goes here
+ *     }
+ *
+ *     public void mousePressed() {
+ *         // do something based on mouse movement
+ *
+ *         // update the screen (run draw once)
+ *         redraw();
+ *     }
+ * }
+ * 
+ * + *

Processing on multiple displays

+ *

I was asked about Processing with multiple displays, and for lack of a + * better place to document it, things will go here.

+ *

You can address both screens by making a window the width of both, + * and the height of the maximum of both screens. In this case, do not use + * present mode, because that's exclusive to one screen. Basically it'll + * give you a PApplet that spans both screens. If using one half to control + * and the other half for graphics, you'd just have to put the 'live' stuff + * on one half of the canvas, the control stuff on the other. This works + * better in windows because on the mac we can't get rid of the menu bar + * unless it's running in present mode.

+ *

For more control, you need to write straight java code that uses p5. + * You can create two windows, that are shown on two separate screens, + * that have their own PApplet. this is just one of the tradeoffs of one of + * the things that we don't support in p5 from within the environment + * itself (we must draw the line somewhere), because of how messy it would + * get to start talking about multiple screens. It's also not that tough to + * do by hand w/ some Java code.

+ */ +public class PApplet extends Applet + implements PConstants, Runnable, + MouseListener, MouseMotionListener, KeyListener, FocusListener +{ + /** + * Full name of the Java version (i.e. 1.5.0_11). + * Prior to 0125, this was only the first three digits. + */ + public static final String javaVersionName = + System.getProperty("java.version"); + + /** + * Version of Java that's in use, whether 1.1 or 1.3 or whatever, + * stored as a float. + *

+ * Note that because this is stored as a float, the values may + * not be exactly 1.3 or 1.4. Instead, make sure you're + * comparing against 1.3f or 1.4f, which will have the same amount + * of error (i.e. 1.40000001). This could just be a double, but + * since Processing only uses floats, it's safer for this to be a float + * because there's no good way to specify a double with the preproc. + */ + public static final float javaVersion = + new Float(javaVersionName.substring(0, 3)).floatValue(); + + /** + * Current platform in use. + *

+ * Equivalent to System.getProperty("os.name"), just used internally. + */ + + /** + * Current platform in use, one of the + * PConstants WINDOWS, MACOSX, MACOS9, LINUX or OTHER. + */ + static public int platform; + + /** + * Name associated with the current 'platform' (see PConstants.platformNames) + */ + //static public String platformName; + + static { + String osname = System.getProperty("os.name"); + + if (osname.indexOf("Mac") != -1) { + platform = MACOSX; + + } else if (osname.indexOf("Windows") != -1) { + platform = WINDOWS; + + } else if (osname.equals("Linux")) { // true for the ibm vm + platform = LINUX; + + } else { + platform = OTHER; + } + } + + /** + * Modifier flags for the shortcut key used to trigger menus. + * (Cmd on Mac OS X, Ctrl on Linux and Windows) + */ + static public final int MENU_SHORTCUT = + Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + + /** The PGraphics renderer associated with this PApplet */ + public PGraphics g; + + //protected Object glock = new Object(); // for sync + + /** The frame containing this applet (if any) */ + public Frame frame; + + /** + * The screen size when the applet was started. + *

+ * Access this via screen.width and screen.height. To make an applet + * run at full screen, use size(screen.width, screen.height). + *

+ * If you have multiple displays, this will be the size of the main + * display. Running full screen across multiple displays isn't + * particularly supported, and requires more monkeying with the values. + * This probably can't/won't be fixed until/unless I get a dual head + * system. + *

+ * Note that this won't update if you change the resolution + * of your screen once the the applet is running. + *

+ * This variable is not static, because future releases need to be better + * at handling multiple displays. + */ + public Dimension screen = + Toolkit.getDefaultToolkit().getScreenSize(); + + /** + * A leech graphics object that is echoing all events. + */ + public PGraphics recorder; + + /** + * Command line options passed in from main(). + *

+ * This does not include the arguments passed in to PApplet itself. + */ + public String args[]; + + /** Path to sketch folder */ + public String sketchPath; //folder; + + /** When debugging headaches */ + static final boolean THREAD_DEBUG = false; + + /** Default width and height for applet when not specified */ + static public final int DEFAULT_WIDTH = 100; + static public final int DEFAULT_HEIGHT = 100; + + /** + * Minimum dimensions for the window holding an applet. + * This varies between platforms, Mac OS X 10.3 can do any height + * but requires at least 128 pixels width. Windows XP has another + * set of limitations. And for all I know, Linux probably lets you + * make windows with negative sizes. + */ + static public final int MIN_WINDOW_WIDTH = 128; + static public final int MIN_WINDOW_HEIGHT = 128; + + /** + * Exception thrown when size() is called the first time. + *

+ * This is used internally so that setup() is forced to run twice + * when the renderer is changed. This is the only way for us to handle + * invoking the new renderer while also in the midst of rendering. + */ + static public class RendererChangeException extends RuntimeException { } + + /** + * true if no size() command has been executed. This is used to wait until + * a size has been set before placing in the window and showing it. + */ + public boolean defaultSize; + + volatile boolean resizeRequest; + volatile int resizeWidth; + volatile int resizeHeight; + + /** + * Pixel buffer from this applet's PGraphics. + *

+ * When used with OpenGL or Java2D, this value will + * be null until loadPixels() has been called. + */ + public int pixels[]; + + /** width of this applet's associated PGraphics */ + public int width; + + /** height of this applet's associated PGraphics */ + public int height; + + /** current x position of the mouse */ + public int mouseX; + + /** current y position of the mouse */ + public int mouseY; + + /** + * Previous x/y position of the mouse. This will be a different value + * when inside a mouse handler (like the mouseMoved() method) versus + * when inside draw(). Inside draw(), pmouseX is updated once each + * frame, but inside mousePressed() and friends, it's updated each time + * an event comes through. Be sure to use only one or the other type of + * means for tracking pmouseX and pmouseY within your sketch, otherwise + * you're gonna run into trouble. + */ + public int pmouseX, pmouseY; + + /** + * previous mouseX/Y for the draw loop, separated out because this is + * separate from the pmouseX/Y when inside the mouse event handlers. + */ + protected int dmouseX, dmouseY; + + /** + * pmouseX/Y for the event handlers (mousePressed(), mouseDragged() etc) + * these are different because mouse events are queued to the end of + * draw, so the previous position has to be updated on each event, + * as opposed to the pmouseX/Y that's used inside draw, which is expected + * to be updated once per trip through draw(). + */ + protected int emouseX, emouseY; + + /** + * Used to set pmouseX/Y to mouseX/Y the first time mouseX/Y are used, + * otherwise pmouseX/Y are always zero, causing a nasty jump. + *

+ * Just using (frameCount == 0) won't work since mouseXxxxx() + * may not be called until a couple frames into things. + */ + public boolean firstMouse; + + /** + * Last mouse button pressed, one of LEFT, CENTER, or RIGHT. + *

+ * If running on Mac OS, a ctrl-click will be interpreted as + * the righthand mouse button (unlike Java, which reports it as + * the left mouse). + */ + public int mouseButton; + + public boolean mousePressed; + public MouseEvent mouseEvent; + + /** + * Last key pressed. + *

+ * If it's a coded key, i.e. UP/DOWN/CTRL/SHIFT/ALT, + * this will be set to CODED (0xffff or 65535). + */ + public char key; + + /** + * When "key" is set to CODED, this will contain a Java key code. + *

+ * For the arrow keys, keyCode will be one of UP, DOWN, LEFT and RIGHT. + * Also available are ALT, CONTROL and SHIFT. A full set of constants + * can be obtained from java.awt.event.KeyEvent, from the VK_XXXX variables. + */ + public int keyCode; + + /** + * true if the mouse is currently pressed. + */ + public boolean keyPressed; + + /** + * the last KeyEvent object passed into a mouse function. + */ + public KeyEvent keyEvent; + + /** + * Gets set to true/false as the applet gains/loses focus. + */ + public boolean focused = false; + + /** + * true if the applet is online. + *

+ * This can be used to test how the applet should behave + * since online situations are different (no file writing, etc). + */ + public boolean online = false; + + /** + * Time in milliseconds when the applet was started. + *

+ * Used by the millis() function. + */ + long millisOffset; + + /** + * The current value of frames per second. + *

+ * The initial value will be 10 fps, and will be updated with each + * frame thereafter. The value is not instantaneous (since that + * wouldn't be very useful since it would jump around so much), + * but is instead averaged (integrated) over several frames. + * As such, this value won't be valid until after 5-10 frames. + */ + public float frameRate = 10; + /** Last time in nanoseconds that frameRate was checked */ + protected long frameRateLastNanos = 0; + + /** As of release 0116, frameRate(60) is called as a default */ + protected float frameRateTarget = 60; + protected long frameRatePeriod = 1000000000L / 60L; + + protected boolean looping; + + /** flag set to true when a redraw is asked for by the user */ + protected boolean redraw; + + /** + * How many frames have been displayed since the applet started. + *

+ * This value is read-only do not attempt to set it, + * otherwise bad things will happen. + *

+ * Inside setup(), frameCount is 0. + * For the first iteration of draw(), frameCount will equal 1. + */ + public int frameCount; + + /** + * true if this applet has had it. + */ + public boolean finished; + + /** + * true if exit() has been called so that things shut down + * once the main thread kicks off. + */ + protected boolean exitCalled; + + Thread thread; + + protected RegisteredMethods sizeMethods; + protected RegisteredMethods preMethods, drawMethods, postMethods; + protected RegisteredMethods mouseEventMethods, keyEventMethods; + protected RegisteredMethods disposeMethods; + + // messages to send if attached as an external vm + + /** + * Position of the upper-lefthand corner of the editor window + * that launched this applet. + */ + static public final String ARGS_EDITOR_LOCATION = "--editor-location"; + + /** + * Location for where to position the applet window on screen. + *

+ * This is used by the editor to when saving the previous applet + * location, or could be used by other classes to launch at a + * specific position on-screen. + */ + static public final String ARGS_EXTERNAL = "--external"; + + static public final String ARGS_LOCATION = "--location"; + + static public final String ARGS_DISPLAY = "--display"; + + static public final String ARGS_BGCOLOR = "--bgcolor"; + + static public final String ARGS_PRESENT = "--present"; + + static public final String ARGS_EXCLUSIVE = "--exclusive"; + + static public final String ARGS_STOP_COLOR = "--stop-color"; + + static public final String ARGS_HIDE_STOP = "--hide-stop"; + + /** + * Allows the user or PdeEditor to set a specific sketch folder path. + *

+ * Used by PdeEditor to pass in the location where saveFrame() + * and all that stuff should write things. + */ + static public final String ARGS_SKETCH_FOLDER = "--sketch-path"; + + /** + * When run externally to a PdeEditor, + * this is sent by the applet when it quits. + */ + //static public final String EXTERNAL_QUIT = "__QUIT__"; + static public final String EXTERNAL_STOP = "__STOP__"; + + /** + * When run externally to a PDE Editor, this is sent by the applet + * whenever the window is moved. + *

+ * This is used so that the editor can re-open the sketch window + * in the same position as the user last left it. + */ + static public final String EXTERNAL_MOVE = "__MOVE__"; + + /** true if this sketch is being run by the PDE */ + boolean external = false; + + + static final String ERROR_MIN_MAX = + "Cannot use min() or max() on an empty array."; + + + // during rev 0100 dev cycle, working on new threading model, + // but need to disable and go conservative with changes in order + // to get pdf and audio working properly first. + // for 0116, the CRUSTY_THREADS are being disabled to fix lots of bugs. + //static final boolean CRUSTY_THREADS = false; //true; + + + public void init() { +// println("Calling init()"); + + // send tab keys through to the PApplet + setFocusTraversalKeysEnabled(false); + + millisOffset = System.currentTimeMillis(); + + finished = false; // just for clarity + + // this will be cleared by draw() if it is not overridden + looping = true; + redraw = true; // draw this guy once + firstMouse = true; + + // these need to be inited before setup + sizeMethods = new RegisteredMethods(); + preMethods = new RegisteredMethods(); + drawMethods = new RegisteredMethods(); + postMethods = new RegisteredMethods(); + mouseEventMethods = new RegisteredMethods(); + keyEventMethods = new RegisteredMethods(); + disposeMethods = new RegisteredMethods(); + + try { + getAppletContext(); + online = true; + } catch (NullPointerException e) { + online = false; + } + + try { + if (sketchPath == null) { + sketchPath = System.getProperty("user.dir"); + } + } catch (Exception e) { } // may be a security problem + + Dimension size = getSize(); + if ((size.width != 0) && (size.height != 0)) { + // When this PApplet is embedded inside a Java application with other + // Component objects, its size() may already be set externally (perhaps + // by a LayoutManager). In this case, honor that size as the default. + // Size of the component is set, just create a renderer. + g = makeGraphics(size.width, size.height, getSketchRenderer(), null, true); + // This doesn't call setSize() or setPreferredSize() because the fact + // that a size was already set means that someone is already doing it. + + } else { + // Set the default size, until the user specifies otherwise + this.defaultSize = true; + int w = getSketchWidth(); + int h = getSketchHeight(); + g = makeGraphics(w, h, getSketchRenderer(), null, true); + // Fire component resize event + setSize(w, h); + setPreferredSize(new Dimension(w, h)); + } + width = g.width; + height = g.height; + + addListeners(); + + // this is automatically called in applets + // though it's here for applications anyway + start(); + } + + + public int getSketchWidth() { + return DEFAULT_WIDTH; + } + + + public int getSketchHeight() { + return DEFAULT_HEIGHT; + } + + + public String getSketchRenderer() { + return JAVA2D; + } + + + /** + * Called by the browser or applet viewer to inform this applet that it + * should start its execution. It is called after the init method and + * each time the applet is revisited in a Web page. + *

+ * Called explicitly via the first call to PApplet.paint(), because + * PAppletGL needs to have a usable screen before getting things rolling. + */ + public void start() { + // When running inside a browser, start() will be called when someone + // returns to a page containing this applet. + // http://dev.processing.org/bugs/show_bug.cgi?id=581 + finished = false; + + if (thread != null) return; + thread = new Thread(this, "Animation Thread"); + thread.start(); + } + + + /** + * Called by the browser or applet viewer to inform + * this applet that it should stop its execution. + *

+ * Unfortunately, there are no guarantees from the Java spec + * when or if stop() will be called (i.e. on browser quit, + * or when moving between web pages), and it's not always called. + */ + public void stop() { + // bringing this back for 0111, hoping it'll help opengl shutdown + finished = true; // why did i comment this out? + + // don't run stop and disposers twice + if (thread == null) return; + thread = null; + + // call to shut down renderer, in case it needs it (pdf does) + if (g != null) g.dispose(); + + // maybe this should be done earlier? might help ensure it gets called + // before the vm just craps out since 1.5 craps out so aggressively. + disposeMethods.handle(); + } + + + /** + * Called by the browser or applet viewer to inform this applet + * that it is being reclaimed and that it should destroy + * any resources that it has allocated. + *

+ * This also attempts to call PApplet.stop(), in case there + * was an inadvertent override of the stop() function by a user. + *

+ * destroy() supposedly gets called as the applet viewer + * is shutting down the applet. stop() is called + * first, and then destroy() to really get rid of things. + * no guarantees on when they're run (on browser quit, or + * when moving between pages), though. + */ + public void destroy() { + ((PApplet)this).stop(); + } + + + /** + * This returns the last width and height specified by the user + * via the size() command. + */ +// public Dimension getPreferredSize() { +// return new Dimension(width, height); +// } + + +// public void addNotify() { +// super.addNotify(); +// println("addNotify()"); +// } + + + + ////////////////////////////////////////////////////////////// + + + public class RegisteredMethods { + int count; + Object objects[]; + Method methods[]; + + + // convenience version for no args + public void handle() { + handle(new Object[] { }); + } + + public void handle(Object oargs[]) { + for (int i = 0; i < count; i++) { + try { + //System.out.println(objects[i] + " " + args); + methods[i].invoke(objects[i], oargs); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public void add(Object object, Method method) { + if (objects == null) { + objects = new Object[5]; + methods = new Method[5]; + } + if (count == objects.length) { + objects = (Object[]) PApplet.expand(objects); + methods = (Method[]) PApplet.expand(methods); +// Object otemp[] = new Object[count << 1]; +// System.arraycopy(objects, 0, otemp, 0, count); +// objects = otemp; +// Method mtemp[] = new Method[count << 1]; +// System.arraycopy(methods, 0, mtemp, 0, count); +// methods = mtemp; + } + objects[count] = object; + methods[count] = method; + count++; + } + + + /** + * Removes first object/method pair matched (and only the first, + * must be called multiple times if object is registered multiple times). + * Does not shrink array afterwards, silently returns if method not found. + */ + public void remove(Object object, Method method) { + int index = findIndex(object, method); + if (index != -1) { + // shift remaining methods by one to preserve ordering + count--; + for (int i = index; i < count; i++) { + objects[i] = objects[i+1]; + methods[i] = methods[i+1]; + } + // clean things out for the gc's sake + objects[count] = null; + methods[count] = null; + } + } + + protected int findIndex(Object object, Method method) { + for (int i = 0; i < count; i++) { + if (objects[i] == object && methods[i].equals(method)) { + //objects[i].equals() might be overridden, so use == for safety + // since here we do care about actual object identity + //methods[i]==method is never true even for same method, so must use + // equals(), this should be safe because of object identity + return i; + } + } + return -1; + } + } + + + public void registerSize(Object o) { + Class methodArgs[] = new Class[] { Integer.TYPE, Integer.TYPE }; + registerWithArgs(sizeMethods, "size", o, methodArgs); + } + + public void registerPre(Object o) { + registerNoArgs(preMethods, "pre", o); + } + + public void registerDraw(Object o) { + registerNoArgs(drawMethods, "draw", o); + } + + public void registerPost(Object o) { + registerNoArgs(postMethods, "post", o); + } + + public void registerMouseEvent(Object o) { + Class methodArgs[] = new Class[] { MouseEvent.class }; + registerWithArgs(mouseEventMethods, "mouseEvent", o, methodArgs); + } + + + public void registerKeyEvent(Object o) { + Class methodArgs[] = new Class[] { KeyEvent.class }; + registerWithArgs(keyEventMethods, "keyEvent", o, methodArgs); + } + + public void registerDispose(Object o) { + registerNoArgs(disposeMethods, "dispose", o); + } + + + protected void registerNoArgs(RegisteredMethods meth, + String name, Object o) { + Class c = o.getClass(); + try { + Method method = c.getMethod(name, new Class[] {}); + meth.add(o, method); + + } catch (NoSuchMethodException nsme) { + die("There is no " + name + "() method in the class " + + o.getClass().getName()); + + } catch (Exception e) { + die("Could not register " + name + " + () for " + o, e); + } + } + + + protected void registerWithArgs(RegisteredMethods meth, + String name, Object o, Class cargs[]) { + Class c = o.getClass(); + try { + Method method = c.getMethod(name, cargs); + meth.add(o, method); + + } catch (NoSuchMethodException nsme) { + die("There is no " + name + "() method in the class " + + o.getClass().getName()); + + } catch (Exception e) { + die("Could not register " + name + " + () for " + o, e); + } + } + + + public void unregisterSize(Object o) { + Class methodArgs[] = new Class[] { Integer.TYPE, Integer.TYPE }; + unregisterWithArgs(sizeMethods, "size", o, methodArgs); + } + + public void unregisterPre(Object o) { + unregisterNoArgs(preMethods, "pre", o); + } + + public void unregisterDraw(Object o) { + unregisterNoArgs(drawMethods, "draw", o); + } + + public void unregisterPost(Object o) { + unregisterNoArgs(postMethods, "post", o); + } + + public void unregisterMouseEvent(Object o) { + Class methodArgs[] = new Class[] { MouseEvent.class }; + unregisterWithArgs(mouseEventMethods, "mouseEvent", o, methodArgs); + } + + public void unregisterKeyEvent(Object o) { + Class methodArgs[] = new Class[] { KeyEvent.class }; + unregisterWithArgs(keyEventMethods, "keyEvent", o, methodArgs); + } + + public void unregisterDispose(Object o) { + unregisterNoArgs(disposeMethods, "dispose", o); + } + + + protected void unregisterNoArgs(RegisteredMethods meth, + String name, Object o) { + Class c = o.getClass(); + try { + Method method = c.getMethod(name, new Class[] {}); + meth.remove(o, method); + } catch (Exception e) { + die("Could not unregister " + name + "() for " + o, e); + } + } + + + protected void unregisterWithArgs(RegisteredMethods meth, + String name, Object o, Class cargs[]) { + Class c = o.getClass(); + try { + Method method = c.getMethod(name, cargs); + meth.remove(o, method); + } catch (Exception e) { + die("Could not unregister " + name + "() for " + o, e); + } + } + + + ////////////////////////////////////////////////////////////// + + + public void setup() { + } + + + public void draw() { + // if no draw method, then shut things down + //System.out.println("no draw method, goodbye"); + finished = true; + } + + + ////////////////////////////////////////////////////////////// + + + protected void resizeRenderer(int iwidth, int iheight) { +// println("resizeRenderer request for " + iwidth + " " + iheight); + if (width != iwidth || height != iheight) { +// println(" former size was " + width + " " + height); + g.setSize(iwidth, iheight); + width = iwidth; + height = iheight; + } + } + + + /** + * Starts up and creates a two-dimensional drawing surface, + * or resizes the current drawing surface. + *

+ * This should be the first thing called inside of setup(). + *

+ * If using Java 1.3 or later, this will default to using + * PGraphics2, the Java2D-based renderer. If using Java 1.1, + * or if PGraphics2 is not available, then PGraphics will be used. + * To set your own renderer, use the other version of the size() + * method that takes a renderer as its last parameter. + *

+ * If called once a renderer has already been set, this will + * use the previous renderer and simply resize it. + */ + public void size(int iwidth, int iheight) { + size(iwidth, iheight, JAVA2D, null); + } + + + public void size(int iwidth, int iheight, String irenderer) { + size(iwidth, iheight, irenderer, null); + } + + + /** + * Creates a new PGraphics object and sets it to the specified size. + * + * Note that you cannot change the renderer once outside of setup(). + * In most cases, you can call size() to give it a new size, + * but you need to always ask for the same renderer, otherwise + * you're gonna run into trouble. + * + * The size() method should *only* be called from inside the setup() or + * draw() methods, so that it is properly run on the main animation thread. + * To change the size of a PApplet externally, use setSize(), which will + * update the component size, and queue a resize of the renderer as well. + */ + public void size(final int iwidth, final int iheight, + String irenderer, String ipath) { + // Run this from the EDT, just cuz it's AWT stuff (or maybe later Swing) + SwingUtilities.invokeLater(new Runnable() { + public void run() { + // Set the preferred size so that the layout managers can handle it + setPreferredSize(new Dimension(iwidth, iheight)); + setSize(iwidth, iheight); + } + }); + + // ensure that this is an absolute path + if (ipath != null) ipath = savePath(ipath); + + String currentRenderer = g.getClass().getName(); + if (currentRenderer.equals(irenderer)) { + // Avoid infinite loop of throwing exception to reset renderer + resizeRenderer(iwidth, iheight); + //redraw(); // will only be called insize draw() + + } else { // renderer is being changed + // otherwise ok to fall through and create renderer below + // the renderer is changing, so need to create a new object + g = makeGraphics(iwidth, iheight, irenderer, ipath, true); + width = iwidth; + height = iheight; + + // fire resize event to make sure the applet is the proper size +// setSize(iwidth, iheight); + // this is the function that will run if the user does their own + // size() command inside setup, so set defaultSize to false. + defaultSize = false; + + // throw an exception so that setup() is called again + // but with a properly sized render + // this is for opengl, which needs a valid, properly sized + // display before calling anything inside setup(). + throw new RendererChangeException(); + } + } + + + /** + * Create an offscreen PGraphics object for drawing. This can be used + * for bitmap or vector images drawing or rendering. + *

+ */ + public PGraphics createGraphics(int iwidth, int iheight, + String irenderer) { + PGraphics pg = makeGraphics(iwidth, iheight, irenderer, null, false); + //pg.parent = this; // make save() work + return pg; + } + + + /** + * Create an offscreen graphics surface for drawing, in this case + * for a renderer that writes to a file (such as PDF or DXF). + * @param ipath can be an absolute or relative path + */ + public PGraphics createGraphics(int iwidth, int iheight, + String irenderer, String ipath) { + if (ipath != null) { + ipath = savePath(ipath); + } + PGraphics pg = makeGraphics(iwidth, iheight, irenderer, ipath, false); + pg.parent = this; // make save() work + return pg; + } + + + /** + * Version of createGraphics() used internally. + * + * @param ipath must be an absolute path, usually set via savePath() + * @oaram applet the parent applet object, this should only be non-null + * in cases where this is the main drawing surface object. + */ + protected PGraphics makeGraphics(int iwidth, int iheight, + String irenderer, String ipath, + boolean iprimary) { + if (irenderer.equals(OPENGL)) { + if (PApplet.platform == WINDOWS) { + String s = System.getProperty("java.version"); + if (s != null) { + if (s.equals("1.5.0_10")) { + System.err.println("OpenGL support is broken with Java 1.5.0_10"); + System.err.println("See http://dev.processing.org" + + "/bugs/show_bug.cgi?id=513 for more info."); + throw new RuntimeException("Please update your Java " + + "installation (see bug #513)"); + } + } + } + } + +// if (irenderer.equals(P2D)) { +// throw new RuntimeException("The P2D renderer is currently disabled, " + +// "please use P3D or JAVA2D."); +// } + + String openglError = + "Before using OpenGL, first select " + + "Import Library > opengl from the Sketch menu."; + + try { + /* + Class rendererClass = Class.forName(irenderer); + + Class constructorParams[] = null; + Object constructorValues[] = null; + + if (ipath == null) { + constructorParams = new Class[] { + Integer.TYPE, Integer.TYPE, PApplet.class + }; + constructorValues = new Object[] { + new Integer(iwidth), new Integer(iheight), this + }; + } else { + constructorParams = new Class[] { + Integer.TYPE, Integer.TYPE, PApplet.class, String.class + }; + constructorValues = new Object[] { + new Integer(iwidth), new Integer(iheight), this, ipath + }; + } + + Constructor constructor = + rendererClass.getConstructor(constructorParams); + PGraphics pg = (PGraphics) constructor.newInstance(constructorValues); + */ + + Class rendererClass = + Thread.currentThread().getContextClassLoader().loadClass(irenderer); + + //Class params[] = null; + //PApplet.println(rendererClass.getConstructors()); + Constructor constructor = rendererClass.getConstructor(new Class[] { }); + PGraphics pg = (PGraphics) constructor.newInstance(); + + pg.setParent(this); + pg.setPrimary(iprimary); + if (ipath != null) pg.setPath(ipath); + pg.setSize(iwidth, iheight); + + // everything worked, return it + return pg; + + } catch (InvocationTargetException ite) { + String msg = ite.getTargetException().getMessage(); + if ((msg != null) && + (msg.indexOf("no jogl in java.library.path") != -1)) { + throw new RuntimeException(openglError + + " (The native library is missing.)"); + + } else { + ite.getTargetException().printStackTrace(); + Throwable target = ite.getTargetException(); + if (platform == MACOSX) target.printStackTrace(System.out); // bug + // neither of these help, or work + //target.printStackTrace(System.err); + //System.err.flush(); + //System.out.println(System.err); // and the object isn't null + throw new RuntimeException(target.getMessage()); + } + + } catch (ClassNotFoundException cnfe) { + if (cnfe.getMessage().indexOf("processing.opengl.PGraphicsGL") != -1) { + throw new RuntimeException(openglError + + " (The library .jar file is missing.)"); + } else { + throw new RuntimeException("You need to use \"Import Library\" " + + "to add " + irenderer + " to your sketch."); + } + + } catch (Exception e) { + //System.out.println("ex3"); + if ((e instanceof IllegalArgumentException) || + (e instanceof NoSuchMethodException) || + (e instanceof IllegalAccessException)) { + e.printStackTrace(); + /* + String msg = "public " + + irenderer.substring(irenderer.lastIndexOf('.') + 1) + + "(int width, int height, PApplet parent" + + ((ipath == null) ? "" : ", String filename") + + ") does not exist."; + */ + String msg = irenderer + " needs to be updated " + + "for the current release of Processing."; + throw new RuntimeException(msg); + + } else { + if (platform == MACOSX) e.printStackTrace(System.out); + throw new RuntimeException(e.getMessage()); + } + } + } + + + /** + * Preferred method of creating new PImage objects, ensures that a + * reference to the parent PApplet is included, which makes save() work + * without needing an absolute path. + */ + public PImage createImage(int wide, int high, int format) { + PImage image = new PImage(wide, high, format); + image.parent = this; // make save() work + return image; + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + public void update(Graphics screen) { + paint(screen); + } + + + //synchronized public void paint(Graphics screen) { // shutting off for 0146 + public void paint(Graphics screen) { + // ignore the very first call to paint, since it's coming + // from the o.s., and the applet will soon update itself anyway. + if (frameCount == 0) { +// println("Skipping frame"); + // paint() may be called more than once before things + // are finally painted to the screen and the thread gets going + return; + } + + // without ignoring the first call, the first several frames + // are confused because paint() gets called in the midst of + // the initial nextFrame() call, so there are multiple + // updates fighting with one another. + + // g.image is synchronized so that draw/loop and paint don't + // try to fight over it. this was causing a randomized slowdown + // that would cut the frameRate into a third on macosx, + // and is probably related to the windows sluggishness bug too + + // make sure the screen is visible and usable + // (also prevents over-drawing when using PGraphicsOpenGL) + if ((g != null) && (g.image != null)) { +// println("inside paint(), screen.drawImage()"); + screen.drawImage(g.image, 0, 0, null); + } + } + + + // active paint method + protected void paint() { + try { + Graphics screen = this.getGraphics(); + if (screen != null) { + if ((g != null) && (g.image != null)) { + screen.drawImage(g.image, 0, 0, null); + } + Toolkit.getDefaultToolkit().sync(); + } + } catch (Exception e) { + // Seen on applet destroy, maybe can ignore? + e.printStackTrace(); + +// } finally { +// if (g != null) { +// g.dispose(); +// } + } + } + + + ////////////////////////////////////////////////////////////// + + + /** + * Main method for the primary animation thread. + * + * Painting in AWT and Swing + */ + public void run() { // not good to make this synchronized, locks things up + long beforeTime = System.nanoTime(); + long overSleepTime = 0L; + + int noDelays = 0; + // Number of frames with a delay of 0 ms before the + // animation thread yields to other running threads. + final int NO_DELAYS_PER_YIELD = 15; + + /* + // this has to be called after the exception is thrown, + // otherwise the supporting libs won't have a valid context to draw to + Object methodArgs[] = + new Object[] { new Integer(width), new Integer(height) }; + sizeMethods.handle(methodArgs); + */ + + while ((Thread.currentThread() == thread) && !finished) { + // Don't resize the renderer from the EDT (i.e. from a ComponentEvent), + // otherwise it may attempt a resize mid-render. + if (resizeRequest) { + resizeRenderer(resizeWidth, resizeHeight); + resizeRequest = false; + } + + // render a single frame + handleDraw(); + + if (frameCount == 1) { + // Call the request focus event once the image is sure to be on + // screen and the component is valid. The OpenGL renderer will + // request focus for its canvas inside beginDraw(). + // http://java.sun.com/j2se/1.4.2/docs/api/java/awt/doc-files/FocusSpec.html + //println("requesting focus"); + requestFocus(); + } + + // wait for update & paint to happen before drawing next frame + // this is necessary since the drawing is sometimes in a + // separate thread, meaning that the next frame will start + // before the update/paint is completed + + long afterTime = System.nanoTime(); + long timeDiff = afterTime - beforeTime; + //System.out.println("time diff is " + timeDiff); + long sleepTime = (frameRatePeriod - timeDiff) - overSleepTime; + + if (sleepTime > 0) { // some time left in this cycle + try { +// Thread.sleep(sleepTime / 1000000L); // nanoseconds -> milliseconds + Thread.sleep(sleepTime / 1000000L, (int) (sleepTime % 1000000L)); + noDelays = 0; // Got some sleep, not delaying anymore + } catch (InterruptedException ex) { } + + overSleepTime = (System.nanoTime() - afterTime) - sleepTime; + //System.out.println(" oversleep is " + overSleepTime); + + } else { // sleepTime <= 0; the frame took longer than the period +// excess -= sleepTime; // store excess time value + overSleepTime = 0L; + + if (noDelays > NO_DELAYS_PER_YIELD) { + Thread.yield(); // give another thread a chance to run + noDelays = 0; + } + } + + beforeTime = System.nanoTime(); + } + + stop(); // call to shutdown libs? + + // If the user called the exit() function, the window should close, + // rather than the sketch just halting. + if (exitCalled) { + exit2(); + } + } + + + //synchronized public void handleDisplay() { + public void handleDraw() { + if (g != null && (looping || redraw)) { + if (!g.canDraw()) { + // Don't draw if the renderer is not yet ready. + // (e.g. OpenGL has to wait for a peer to be on screen) + return; + } + + //System.out.println("handleDraw() " + frameCount); + + g.beginDraw(); + if (recorder != null) { + recorder.beginDraw(); + } + + long now = System.nanoTime(); + + if (frameCount == 0) { + try { + //println("Calling setup()"); + setup(); + //println("Done with setup()"); + + } catch (RendererChangeException e) { + // Give up, instead set the new renderer and re-attempt setup() + return; + } + this.defaultSize = false; + + } else { // frameCount > 0, meaning an actual draw() + // update the current frameRate + double rate = 1000000.0 / ((now - frameRateLastNanos) / 1000000.0); + float instantaneousRate = (float) rate / 1000.0f; + frameRate = (frameRate * 0.9f) + (instantaneousRate * 0.1f); + + preMethods.handle(); + + // use dmouseX/Y as previous mouse pos, since this is the + // last position the mouse was in during the previous draw. + pmouseX = dmouseX; + pmouseY = dmouseY; + + //println("Calling draw()"); + draw(); + //println("Done calling draw()"); + + // dmouseX/Y is updated only once per frame (unlike emouseX/Y) + dmouseX = mouseX; + dmouseY = mouseY; + + // these are called *after* loop so that valid + // drawing commands can be run inside them. it can't + // be before, since a call to background() would wipe + // out anything that had been drawn so far. + dequeueMouseEvents(); + dequeueKeyEvents(); + + drawMethods.handle(); + + redraw = false; // unset 'redraw' flag in case it was set + // (only do this once draw() has run, not just setup()) + + } + + g.endDraw(); + if (recorder != null) { + recorder.endDraw(); + } + + frameRateLastNanos = now; + frameCount++; + + // Actively render the screen + paint(); + +// repaint(); +// getToolkit().sync(); // force repaint now (proper method) + + postMethods.handle(); + } + } + + + ////////////////////////////////////////////////////////////// + + + + synchronized public void redraw() { + if (!looping) { + redraw = true; +// if (thread != null) { +// // wake from sleep (necessary otherwise it'll be +// // up to 10 seconds before update) +// if (CRUSTY_THREADS) { +// thread.interrupt(); +// } else { +// synchronized (blocker) { +// blocker.notifyAll(); +// } +// } +// } + } + } + + + synchronized public void loop() { + if (!looping) { + looping = true; + } + } + + + synchronized public void noLoop() { + if (looping) { + looping = false; + } + } + + + ////////////////////////////////////////////////////////////// + + + public void addListeners() { + addMouseListener(this); + addMouseMotionListener(this); + addKeyListener(this); + addFocusListener(this); + + addComponentListener(new ComponentAdapter() { + public void componentResized(ComponentEvent e) { + Component c = e.getComponent(); + //System.out.println("componentResized() " + c); + Rectangle bounds = c.getBounds(); + resizeRequest = true; + resizeWidth = bounds.width; + resizeHeight = bounds.height; + } + }); + } + + + ////////////////////////////////////////////////////////////// + + + MouseEvent mouseEventQueue[] = new MouseEvent[10]; + int mouseEventCount; + + protected void enqueueMouseEvent(MouseEvent e) { + synchronized (mouseEventQueue) { + if (mouseEventCount == mouseEventQueue.length) { + MouseEvent temp[] = new MouseEvent[mouseEventCount << 1]; + System.arraycopy(mouseEventQueue, 0, temp, 0, mouseEventCount); + mouseEventQueue = temp; + } + mouseEventQueue[mouseEventCount++] = e; + } + } + + protected void dequeueMouseEvents() { + synchronized (mouseEventQueue) { + for (int i = 0; i < mouseEventCount; i++) { + mouseEvent = mouseEventQueue[i]; + handleMouseEvent(mouseEvent); + } + mouseEventCount = 0; + } + } + + + /** + * Actually take action based on a mouse event. + * Internally updates mouseX, mouseY, mousePressed, and mouseEvent. + * Then it calls the event type with no params, + * i.e. mousePressed() or mouseReleased() that the user may have + * overloaded to do something more useful. + */ + protected void handleMouseEvent(MouseEvent event) { + int id = event.getID(); + + // http://dev.processing.org/bugs/show_bug.cgi?id=170 + // also prevents mouseExited() on the mac from hosing the mouse + // position, because x/y are bizarre values on the exit event. + // see also the id check below.. both of these go together + if ((id == MouseEvent.MOUSE_DRAGGED) || + (id == MouseEvent.MOUSE_MOVED)) { + pmouseX = emouseX; + pmouseY = emouseY; + mouseX = event.getX(); + mouseY = event.getY(); + } + + mouseEvent = event; + + int modifiers = event.getModifiers(); + if ((modifiers & InputEvent.BUTTON1_MASK) != 0) { + mouseButton = LEFT; + } else if ((modifiers & InputEvent.BUTTON2_MASK) != 0) { + mouseButton = CENTER; + } else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) { + mouseButton = RIGHT; + } + // if running on macos, allow ctrl-click as right mouse + if (platform == MACOSX) { + if (mouseEvent.isPopupTrigger()) { + mouseButton = RIGHT; + } + } + + mouseEventMethods.handle(new Object[] { event }); + + // this used to only be called on mouseMoved and mouseDragged + // change it back if people run into trouble + if (firstMouse) { + pmouseX = mouseX; + pmouseY = mouseY; + dmouseX = mouseX; + dmouseY = mouseY; + firstMouse = false; + } + + //println(event); + + switch (id) { + case MouseEvent.MOUSE_PRESSED: + mousePressed = true; + mousePressed(); + break; + case MouseEvent.MOUSE_RELEASED: + mousePressed = false; + mouseReleased(); + break; + case MouseEvent.MOUSE_CLICKED: + mouseClicked(); + break; + case MouseEvent.MOUSE_DRAGGED: + mouseDragged(); + break; + case MouseEvent.MOUSE_MOVED: + mouseMoved(); + break; + } + + if ((id == MouseEvent.MOUSE_DRAGGED) || + (id == MouseEvent.MOUSE_MOVED)) { + emouseX = mouseX; + emouseY = mouseY; + } + } + + + /** + * Figure out how to process a mouse event. When loop() has been + * called, the events will be queued up until drawing is complete. + * If noLoop() has been called, then events will happen immediately. + */ + protected void checkMouseEvent(MouseEvent event) { + if (looping) { + enqueueMouseEvent(event); + } else { + handleMouseEvent(event); + } + } + + + /** + * If you override this or any function that takes a "MouseEvent e" + * without calling its super.mouseXxxx() then mouseX, mouseY, + * mousePressed, and mouseEvent will no longer be set. + */ + public void mousePressed(MouseEvent e) { + checkMouseEvent(e); + } + + public void mouseReleased(MouseEvent e) { + checkMouseEvent(e); + } + + public void mouseClicked(MouseEvent e) { + checkMouseEvent(e); + } + + public void mouseEntered(MouseEvent e) { + checkMouseEvent(e); + } + + public void mouseExited(MouseEvent e) { + checkMouseEvent(e); + } + + public void mouseDragged(MouseEvent e) { + checkMouseEvent(e); + } + + public void mouseMoved(MouseEvent e) { + checkMouseEvent(e); + } + + + /** + * Mouse has been pressed, and should be considered "down" + * until mouseReleased() is called. If you must, use + * int button = mouseEvent.getButton(); + * to figure out which button was clicked. It will be one of: + * MouseEvent.BUTTON1, MouseEvent.BUTTON2, MouseEvent.BUTTON3 + * Note, however, that this is completely inconsistent across + * platforms. + */ + public void mousePressed() { } + + /** + * Mouse button has been released. + */ + public void mouseReleased() { } + + /** + * When the mouse is clicked, mousePressed() will be called, + * then mouseReleased(), then mouseClicked(). Note that + * mousePressed is already false inside of mouseClicked(). + */ + public void mouseClicked() { } + + /** + * Mouse button is pressed and the mouse has been dragged. + */ + public void mouseDragged() { } + + /** + * Mouse button is not pressed but the mouse has changed locations. + */ + public void mouseMoved() { } + + + ////////////////////////////////////////////////////////////// + + + KeyEvent keyEventQueue[] = new KeyEvent[10]; + int keyEventCount; + + protected void enqueueKeyEvent(KeyEvent e) { + synchronized (keyEventQueue) { + if (keyEventCount == keyEventQueue.length) { + KeyEvent temp[] = new KeyEvent[keyEventCount << 1]; + System.arraycopy(keyEventQueue, 0, temp, 0, keyEventCount); + keyEventQueue = temp; + } + keyEventQueue[keyEventCount++] = e; + } + } + + protected void dequeueKeyEvents() { + synchronized (keyEventQueue) { + for (int i = 0; i < keyEventCount; i++) { + keyEvent = keyEventQueue[i]; + handleKeyEvent(keyEvent); + } + keyEventCount = 0; + } + } + + + protected void handleKeyEvent(KeyEvent event) { + keyEvent = event; + key = event.getKeyChar(); + keyCode = event.getKeyCode(); + + keyEventMethods.handle(new Object[] { event }); + + switch (event.getID()) { + case KeyEvent.KEY_PRESSED: + keyPressed = true; + keyPressed(); + break; + case KeyEvent.KEY_RELEASED: + keyPressed = false; + keyReleased(); + break; + case KeyEvent.KEY_TYPED: + keyTyped(); + break; + } + + // if someone else wants to intercept the key, they should + // set key to zero (or something besides the ESC). + if (event.getID() == KeyEvent.KEY_PRESSED) { + if (key == KeyEvent.VK_ESCAPE) { + exit(); + } + // When running tethered to the Processing application, respond to + // Ctrl-W (or Cmd-W) events by closing the sketch. Disable this behavior + // when running independently, because this sketch may be one component + // embedded inside an application that has its own close behavior. + if (external && + event.getModifiers() == MENU_SHORTCUT && + event.getKeyCode() == 'W') { + exit(); + } + } + } + + + protected void checkKeyEvent(KeyEvent event) { + if (looping) { + enqueueKeyEvent(event); + } else { + handleKeyEvent(event); + } + } + + + /** + * Overriding keyXxxxx(KeyEvent e) functions will cause the 'key', + * 'keyCode', and 'keyEvent' variables to no longer work; + * key events will no longer be queued until the end of draw(); + * and the keyPressed(), keyReleased() and keyTyped() methods + * will no longer be called. + */ + public void keyPressed(KeyEvent e) { checkKeyEvent(e); } + public void keyReleased(KeyEvent e) { checkKeyEvent(e); } + public void keyTyped(KeyEvent e) { checkKeyEvent(e); } + + + /** + * Called each time a single key on the keyboard is pressed. + * Because of how operating systems handle key repeats, holding + * down a key will cause multiple calls to keyPressed(), because + * the OS repeat takes over. + *

+ * Examples for key handling: + * (Tested on Windows XP, please notify if different on other + * platforms, I have a feeling Mac OS and Linux may do otherwise) + *

+   * 1. Pressing 'a' on the keyboard:
+   *    keyPressed  with key == 'a' and keyCode == 'A'
+   *    keyTyped    with key == 'a' and keyCode ==  0
+   *    keyReleased with key == 'a' and keyCode == 'A'
+   *
+   * 2. Pressing 'A' on the keyboard:
+   *    keyPressed  with key == 'A' and keyCode == 'A'
+   *    keyTyped    with key == 'A' and keyCode ==  0
+   *    keyReleased with key == 'A' and keyCode == 'A'
+   *
+   * 3. Pressing 'shift', then 'a' on the keyboard (caps lock is off):
+   *    keyPressed  with key == CODED and keyCode == SHIFT
+   *    keyPressed  with key == 'A'   and keyCode == 'A'
+   *    keyTyped    with key == 'A'   and keyCode == 0
+   *    keyReleased with key == 'A'   and keyCode == 'A'
+   *    keyReleased with key == CODED and keyCode == SHIFT
+   *
+   * 4. Holding down the 'a' key.
+   *    The following will happen several times,
+   *    depending on your machine's "key repeat rate" settings:
+   *    keyPressed  with key == 'a' and keyCode == 'A'
+   *    keyTyped    with key == 'a' and keyCode ==  0
+   *    When you finally let go, you'll get:
+   *    keyReleased with key == 'a' and keyCode == 'A'
+   *
+   * 5. Pressing and releasing the 'shift' key
+   *    keyPressed  with key == CODED and keyCode == SHIFT
+   *    keyReleased with key == CODED and keyCode == SHIFT
+   *    (note there is no keyTyped)
+   *
+   * 6. Pressing the tab key in an applet with Java 1.4 will
+   *    normally do nothing, but PApplet dynamically shuts
+   *    this behavior off if Java 1.4 is in use (tested 1.4.2_05 Windows).
+   *    Java 1.1 (Microsoft VM) passes the TAB key through normally.
+   *    Not tested on other platforms or for 1.3.
+   * 
+ */ + public void keyPressed() { } + + + /** + * See keyPressed(). + */ + public void keyReleased() { } + + + /** + * Only called for "regular" keys like letters, + * see keyPressed() for full documentation. + */ + public void keyTyped() { } + + + ////////////////////////////////////////////////////////////// + + // i am focused man, and i'm not afraid of death. + // and i'm going all out. i circle the vultures in a van + // and i run the block. + + + public void focusGained() { } + + public void focusGained(FocusEvent e) { + focused = true; + focusGained(); + } + + + public void focusLost() { } + + public void focusLost(FocusEvent e) { + focused = false; + focusLost(); + } + + + ////////////////////////////////////////////////////////////// + + // getting the time + + + /** + * Get the number of milliseconds since the applet started. + *

+ * This is a function, rather than a variable, because it may + * change multiple times per frame. + */ + public int millis() { + return (int) (System.currentTimeMillis() - millisOffset); + } + + /** Seconds position of the current time. */ + static public int second() { + return Calendar.getInstance().get(Calendar.SECOND); + } + + /** Minutes position of the current time. */ + static public int minute() { + return Calendar.getInstance().get(Calendar.MINUTE); + } + + /** + * Hour position of the current time in international format (0-23). + *

+ * To convert this value to American time:
+ *

int yankeeHour = (hour() % 12);
+   * if (yankeeHour == 0) yankeeHour = 12;
+ */ + static public int hour() { + return Calendar.getInstance().get(Calendar.HOUR_OF_DAY); + } + + /** + * Get the current day of the month (1 through 31). + *

+ * If you're looking for the day of the week (M-F or whatever) + * or day of the year (1..365) then use java's Calendar.get() + */ + static public int day() { + return Calendar.getInstance().get(Calendar.DAY_OF_MONTH); + } + + /** + * Get the current month in range 1 through 12. + */ + static public int month() { + // months are number 0..11 so change to colloquial 1..12 + return Calendar.getInstance().get(Calendar.MONTH) + 1; + } + + /** + * Get the current year. + */ + static public int year() { + return Calendar.getInstance().get(Calendar.YEAR); + } + + + ////////////////////////////////////////////////////////////// + + // controlling time (playing god) + + + /** + * The delay() function causes the program to halt for a specified time. + * Delay times are specified in thousandths of a second. For example, + * running delay(3000) will stop the program for three seconds and + * delay(500) will stop the program for a half-second. Remember: the + * display window is updated only at the end of draw(), so putting more + * than one delay() inside draw() will simply add them together and the new + * frame will be drawn when the total delay is over. + *

+ * I'm not sure if this is even helpful anymore, as the screen isn't + * updated before or after the delay, meaning which means it just + * makes the app lock up temporarily. + */ + public void delay(int napTime) { + if (frameCount != 0) { + if (napTime > 0) { + try { + Thread.sleep(napTime); + } catch (InterruptedException e) { } + } + } + } + + + /** + * Set a target frameRate. This will cause delay() to be called + * after each frame so that the sketch synchronizes to a particular speed. + * Note that this only sets the maximum frame rate, it cannot be used to + * make a slow sketch go faster. Sketches have no default frame rate + * setting, and will attempt to use maximum processor power to achieve + * maximum speed. + */ + public void frameRate(float newRateTarget) { + frameRateTarget = newRateTarget; + frameRatePeriod = (long) (1000000000.0 / frameRateTarget); + } + + + ////////////////////////////////////////////////////////////// + + + /** + * Get a param from the web page, or (eventually) + * from a properties file. + */ + public String param(String what) { + if (online) { + return getParameter(what); + + } else { + System.err.println("param() only works inside a web browser"); + } + return null; + } + + + /** + * Show status in the status bar of a web browser, or in the + * System.out console. Eventually this might show status in the + * p5 environment itself, rather than relying on the console. + */ + public void status(String what) { + if (online) { + showStatus(what); + + } else { + System.out.println(what); // something more interesting? + } + } + + + public void link(String here) { + link(here, null); + } + + + /** + * Link to an external page without all the muss. + *

+ * When run with an applet, uses the browser to open the url, + * for applications, attempts to launch a browser with the url. + *

+ * Works on Mac OS X and Windows. For Linux, use: + *

open(new String[] { "firefox", url });
+ * or whatever you want as your browser, since Linux doesn't + * yet have a standard method for launching URLs. + */ + public void link(String url, String frameTitle) { + if (online) { + try { + if (frameTitle == null) { + getAppletContext().showDocument(new URL(url)); + } else { + getAppletContext().showDocument(new URL(url), frameTitle); + } + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Could not open " + url); + } + } else { + try { + if (platform == WINDOWS) { + // the following uses a shell execute to launch the .html file + // note that under cygwin, the .html files have to be chmodded +x + // after they're unpacked from the zip file. i don't know why, + // and don't understand what this does in terms of windows + // permissions. without the chmod, the command prompt says + // "Access is denied" in both cygwin and the "dos" prompt. + //Runtime.getRuntime().exec("cmd /c " + currentDir + "\\reference\\" + + // referenceFile + ".html"); + + // replace ampersands with control sequence for DOS. + // solution contributed by toxi on the bugs board. + url = url.replaceAll("&","^&"); + + // open dos prompt, give it 'start' command, which will + // open the url properly. start by itself won't work since + // it appears to need cmd + Runtime.getRuntime().exec("cmd /c start " + url); + + } else if (platform == MACOSX) { + //com.apple.mrj.MRJFileUtils.openURL(url); + try { + Class mrjFileUtils = Class.forName("com.apple.mrj.MRJFileUtils"); + Method openMethod = + mrjFileUtils.getMethod("openURL", new Class[] { String.class }); + openMethod.invoke(null, new Object[] { url }); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + //throw new RuntimeException("Can't open URLs for this platform"); + // Just pass it off to open() and hope for the best + open(url); + } + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Could not open " + url); + } + } + } + + + /** + * Attempt to open a file using the platform's shell. + */ + static public void open(String filename) { + open(new String[] { filename }); + } + + + static String openLauncher; + + /** + * Launch a process using a platforms shell. This version uses an array + * to make it easier to deal with spaces in the individual elements. + * (This avoids the situation of trying to put single or double quotes + * around different bits). + */ + static public Process open(String argv[]) { + String[] params = null; + + if (platform == WINDOWS) { + // just launching the .html file via the shell works + // but make sure to chmod +x the .html files first + // also place quotes around it in case there's a space + // in the user.dir part of the url + params = new String[] { "cmd", "/c" }; + + } else if (platform == MACOSX) { + params = new String[] { "open" }; + + } else if (platform == LINUX) { + if (openLauncher == null) { + // Attempt to use gnome-open + try { + Process p = Runtime.getRuntime().exec(new String[] { "gnome-open" }); + /*int result =*/ p.waitFor(); + // Not installed will throw an IOException (JDK 1.4.2, Ubuntu 7.04) + openLauncher = "gnome-open"; + } catch (Exception e) { } + } + if (openLauncher == null) { + // Attempt with kde-open + try { + Process p = Runtime.getRuntime().exec(new String[] { "kde-open" }); + /*int result =*/ p.waitFor(); + openLauncher = "kde-open"; + } catch (Exception e) { } + } + if (openLauncher == null) { + System.err.println("Could not find gnome-open or kde-open, " + + "the open() command may not work."); + } + if (openLauncher != null) { + params = new String[] { openLauncher }; + } + //} else { // give up and just pass it to Runtime.exec() + //open(new String[] { filename }); + //params = new String[] { filename }; + } + if (params != null) { + // If the 'open', 'gnome-open' or 'cmd' are already included + if (params[0].equals(argv[0])) { + // then don't prepend those params again + return exec(argv); + } else { + params = concat(params, argv); + return exec(params); + } + } else { + return exec(argv); + } + } + + + static public Process exec(String[] argv) { + try { + return Runtime.getRuntime().exec(argv); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Could not open " + join(argv, ' ')); + } + } + + + ////////////////////////////////////////////////////////////// + + + /** + * Function for an applet/application to kill itself and + * display an error. Mostly this is here to be improved later. + */ + public void die(String what) { + stop(); + throw new RuntimeException(what); + } + + + /** + * Same as above but with an exception. Also needs work. + */ + public void die(String what, Exception e) { + if (e != null) e.printStackTrace(); + die(what); + } + + + /** + * Call to safely exit the sketch when finished. For instance, + * to render a single frame, save it, and quit. + */ + public void exit() { + if (thread == null) { + // exit immediately, stop() has already been called, + // meaning that the main thread has long since exited + exit2(); + + } else if (looping) { + // stop() will be called as the thread exits + finished = true; + // tell the code to call exit2() to do a System.exit() + // once the next draw() has completed + exitCalled = true; + + } else if (!looping) { + // if not looping, need to call stop explicitly, + // because the main thread will be sleeping + stop(); + + // now get out + exit2(); + } + } + + + void exit2() { + try { + System.exit(0); + } catch (SecurityException e) { + // don't care about applet security exceptions + } + } + + + + ////////////////////////////////////////////////////////////// + + + public void method(String name) { +// final Object o = this; +// final Class c = getClass(); + try { + Method method = getClass().getMethod(name, new Class[] {}); + method.invoke(this, new Object[] { }); + + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.getTargetException().printStackTrace(); + } catch (NoSuchMethodException nsme) { + System.err.println("There is no " + name + "() method " + + "in the class " + getClass().getName()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + public void thread(final String name) { + Thread later = new Thread() { + public void run() { + method(name); + } + }; + later.start(); + } + + + /* + public void thread(String name) { + final Object o = this; + final Class c = getClass(); + try { + final Method method = c.getMethod(name, new Class[] {}); + Thread later = new Thread() { + public void run() { + try { + method.invoke(o, new Object[] { }); + + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.getTargetException().printStackTrace(); + } + } + }; + later.start(); + + } catch (NoSuchMethodException nsme) { + System.err.println("There is no " + name + "() method " + + "in the class " + getClass().getName()); + + } catch (Exception e) { + e.printStackTrace(); + } + } + */ + + + + ////////////////////////////////////////////////////////////// + + // SCREEN GRABASS + + + /** + * Intercepts any relative paths to make them absolute (relative + * to the sketch folder) before passing to save() in PImage. + * (Changed in 0100) + */ + public void save(String filename) { + g.save(savePath(filename)); + } + + + /** + * Grab an image of what's currently in the drawing area and save it + * as a .tif or .tga file. + *

+ * Best used just before endDraw() at the end of your draw(). + * This can only create .tif or .tga images, so if neither extension + * is specified it defaults to writing a tiff and adds a .tif suffix. + */ + public void saveFrame() { + try { + g.save(savePath("screen-" + nf(frameCount, 4) + ".tif")); + } catch (SecurityException se) { + System.err.println("Can't use saveFrame() when running in a browser, " + + "unless using a signed applet."); + } + } + + + /** + * Save the current frame as a .tif or .tga image. + *

+ * The String passed in can contain a series of # signs + * that will be replaced with the screengrab number. + *

+   * i.e. saveFrame("blah-####.tif");
+   *      // saves a numbered tiff image, replacing the
+   *      // #### signs with zeros and the frame number 
+ */ + public void saveFrame(String what) { + try { + g.save(savePath(insertFrame(what))); + } catch (SecurityException se) { + System.err.println("Can't use saveFrame() when running in a browser, " + + "unless using a signed applet."); + } + } + + + /** + * Check a string for #### signs to see if the frame number should be + * inserted. Used for functions like saveFrame() and beginRecord() to + * replace the # marks with the frame number. If only one # is used, + * it will be ignored, under the assumption that it's probably not + * intended to be the frame number. + */ + protected String insertFrame(String what) { + int first = what.indexOf('#'); + int last = what.lastIndexOf('#'); + + if ((first != -1) && (last - first > 0)) { + String prefix = what.substring(0, first); + int count = last - first + 1; + String suffix = what.substring(last + 1); + return prefix + nf(frameCount, count) + suffix; + } + return what; // no change + } + + + + ////////////////////////////////////////////////////////////// + + // CURSOR + + // + + + int cursorType = ARROW; // cursor type + boolean cursorVisible = true; // cursor visibility flag + PImage invisibleCursor; + + + /** + * Set the cursor type + */ + public void cursor(int cursorType) { + setCursor(Cursor.getPredefinedCursor(cursorType)); + cursorVisible = true; + this.cursorType = cursorType; + } + + + /** + * Replace the cursor with the specified PImage. The x- and y- + * coordinate of the center will be the center of the image. + */ + public void cursor(PImage image) { + cursor(image, image.width/2, image.height/2); + } + + + /** + * Set a custom cursor to an image with a specific hotspot. + * Only works with JDK 1.2 and later. + * Currently seems to be broken on Java 1.4 for Mac OS X + *

+ * Based on code contributed by Amit Pitaru, plus additional + * code to handle Java versions via reflection by Jonathan Feinberg. + * Reflection removed for release 0128 and later. + */ + public void cursor(PImage image, int hotspotX, int hotspotY) { + // don't set this as cursor type, instead use cursor_type + // to save the last cursor used in case cursor() is called + //cursor_type = Cursor.CUSTOM_CURSOR; + Image jimage = + createImage(new MemoryImageSource(image.width, image.height, + image.pixels, 0, image.width)); + Point hotspot = new Point(hotspotX, hotspotY); + Toolkit tk = Toolkit.getDefaultToolkit(); + Cursor cursor = tk.createCustomCursor(jimage, hotspot, "Custom Cursor"); + setCursor(cursor); + cursorVisible = true; + } + + + /** + * Show the cursor after noCursor() was called. + * Notice that the program remembers the last set cursor type + */ + public void cursor() { + // maybe should always set here? seems dangerous, since + // it's likely that java will set the cursor to something + // else on its own, and the applet will be stuck b/c bagel + // thinks that the cursor is set to one particular thing + if (!cursorVisible) { + cursorVisible = true; + setCursor(Cursor.getPredefinedCursor(cursorType)); + } + } + + + /** + * Hide the cursor by creating a transparent image + * and using it as a custom cursor. + */ + public void noCursor() { + if (!cursorVisible) return; // don't hide if already hidden. + + if (invisibleCursor == null) { + invisibleCursor = new PImage(16, 16, ARGB); + } + // was formerly 16x16, but the 0x0 was added by jdf as a fix + // for macosx, which wasn't honoring the invisible cursor + cursor(invisibleCursor, 8, 8); + cursorVisible = false; + } + + + ////////////////////////////////////////////////////////////// + + + static public void print(byte what) { + System.out.print(what); + System.out.flush(); + } + + static public void print(boolean what) { + System.out.print(what); + System.out.flush(); + } + + static public void print(char what) { + System.out.print(what); + System.out.flush(); + } + + static public void print(int what) { + System.out.print(what); + System.out.flush(); + } + + static public void print(float what) { + System.out.print(what); + System.out.flush(); + } + + static public void print(String what) { + System.out.print(what); + System.out.flush(); + } + + static public void print(Object what) { + if (what == null) { + // special case since this does fuggly things on > 1.1 + System.out.print("null"); + } else { + System.out.println(what.toString()); + } + } + + // + + static public void println() { + System.out.println(); + } + + // + + static public void println(byte what) { + print(what); System.out.println(); + } + + static public void println(boolean what) { + print(what); System.out.println(); + } + + static public void println(char what) { + print(what); System.out.println(); + } + + static public void println(int what) { + print(what); System.out.println(); + } + + static public void println(float what) { + print(what); System.out.println(); + } + + static public void println(String what) { + print(what); System.out.println(); + } + + static public void println(Object what) { + if (what == null) { + // special case since this does fuggly things on > 1.1 + System.out.println("null"); + + } else { + String name = what.getClass().getName(); + if (name.charAt(0) == '[') { + switch (name.charAt(1)) { + case '[': + // don't even mess with multi-dimensional arrays (case '[') + // or anything else that's not int, float, boolean, char + System.out.println(what); + break; + + case 'L': + // print a 1D array of objects as individual elements + Object poo[] = (Object[]) what; + for (int i = 0; i < poo.length; i++) { + if (poo[i] instanceof String) { + System.out.println("[" + i + "] \"" + poo[i] + "\""); + } else { + System.out.println("[" + i + "] " + poo[i]); + } + } + break; + + case 'Z': // boolean + boolean zz[] = (boolean[]) what; + for (int i = 0; i < zz.length; i++) { + System.out.println("[" + i + "] " + zz[i]); + } + break; + + case 'B': // byte + byte bb[] = (byte[]) what; + for (int i = 0; i < bb.length; i++) { + System.out.println("[" + i + "] " + bb[i]); + } + break; + + case 'C': // char + char cc[] = (char[]) what; + for (int i = 0; i < cc.length; i++) { + System.out.println("[" + i + "] '" + cc[i] + "'"); + } + break; + + case 'I': // int + int ii[] = (int[]) what; + for (int i = 0; i < ii.length; i++) { + System.out.println("[" + i + "] " + ii[i]); + } + break; + + case 'F': // float + float ff[] = (float[]) what; + for (int i = 0; i < ff.length; i++) { + System.out.println("[" + i + "] " + ff[i]); + } + break; + + /* + case 'D': // double + double dd[] = (double[]) what; + for (int i = 0; i < dd.length; i++) { + System.out.println("[" + i + "] " + dd[i]); + } + break; + */ + + default: + System.out.println(what); + } + } else { // not an array + System.out.println(what); + } + } + } + + // + + /* + // not very useful, because it only works for public (and protected?) + // fields of a class, not local variables to methods + public void printvar(String name) { + try { + Field field = getClass().getDeclaredField(name); + println(name + " = " + field.get(this)); + } catch (Exception e) { + e.printStackTrace(); + } + } + */ + + + ////////////////////////////////////////////////////////////// + + // MATH + + // lots of convenience methods for math with floats. + // doubles are overkill for processing applets, and casting + // things all the time is annoying, thus the functions below. + + + static public final float abs(float n) { + return (n < 0) ? -n : n; + } + + static public final int abs(int n) { + return (n < 0) ? -n : n; + } + + static public final float sq(float a) { + return a*a; + } + + static public final float sqrt(float a) { + return (float)Math.sqrt(a); + } + + static public final float log(float a) { + return (float)Math.log(a); + } + + static public final float exp(float a) { + return (float)Math.exp(a); + } + + static public final float pow(float a, float b) { + return (float)Math.pow(a, b); + } + + + static public final int max(int a, int b) { + return (a > b) ? a : b; + } + + static public final float max(float a, float b) { + return (a > b) ? a : b; + } + + + static public final int max(int a, int b, int c) { + return (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c); + } + + static public final float max(float a, float b, float c) { + return (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c); + } + + + /** + * Find the maximum value in an array. + * Throws an ArrayIndexOutOfBoundsException if the array is length 0. + * @param list the source array + * @return The maximum value + */ + static public final int max(int[] list) { + if (list.length == 0) { + throw new ArrayIndexOutOfBoundsException(ERROR_MIN_MAX); + } + int max = list[0]; + for (int i = 1; i < list.length; i++) { + if (list[i] > max) max = list[i]; + } + return max; + } + + /** + * Find the maximum value in an array. + * Throws an ArrayIndexOutOfBoundsException if the array is length 0. + * @param list the source array + * @return The maximum value + */ + static public final float max(float[] list) { + if (list.length == 0) { + throw new ArrayIndexOutOfBoundsException(ERROR_MIN_MAX); + } + float max = list[0]; + for (int i = 1; i < list.length; i++) { + if (list[i] > max) max = list[i]; + } + return max; + } + + + static public final int min(int a, int b) { + return (a < b) ? a : b; + } + + static public final float min(float a, float b) { + return (a < b) ? a : b; + } + + + static public final int min(int a, int b, int c) { + return (a < b) ? ((a < c) ? a : c) : ((b < c) ? b : c); + } + + static public final float min(float a, float b, float c) { + return (a < b) ? ((a < c) ? a : c) : ((b < c) ? b : c); + } + + + /** + * Find the minimum value in an array. + * Throws an ArrayIndexOutOfBoundsException if the array is length 0. + * @param list the source array + * @return The minimum value + */ + static public final int min(int[] list) { + if (list.length == 0) { + throw new ArrayIndexOutOfBoundsException(ERROR_MIN_MAX); + } + int min = list[0]; + for (int i = 1; i < list.length; i++) { + if (list[i] < min) min = list[i]; + } + return min; + } + /** + * Find the minimum value in an array. + * Throws an ArrayIndexOutOfBoundsException if the array is length 0. + * @param list the source array + * @return The minimum value + */ + static public final float min(float[] list) { + if (list.length == 0) { + throw new ArrayIndexOutOfBoundsException(ERROR_MIN_MAX); + } + float min = list[0]; + for (int i = 1; i < list.length; i++) { + if (list[i] < min) min = list[i]; + } + return min; + } + + + static public final int constrain(int amt, int low, int high) { + return (amt < low) ? low : ((amt > high) ? high : amt); + } + + static public final float constrain(float amt, float low, float high) { + return (amt < low) ? low : ((amt > high) ? high : amt); + } + + + static public final float sin(float angle) { + return (float)Math.sin(angle); + } + + static public final float cos(float angle) { + return (float)Math.cos(angle); + } + + static public final float tan(float angle) { + return (float)Math.tan(angle); + } + + + static public final float asin(float value) { + return (float)Math.asin(value); + } + + static public final float acos(float value) { + return (float)Math.acos(value); + } + + static public final float atan(float value) { + return (float)Math.atan(value); + } + + static public final float atan2(float a, float b) { + return (float)Math.atan2(a, b); + } + + + static public final float degrees(float radians) { + return radians * RAD_TO_DEG; + } + + static public final float radians(float degrees) { + return degrees * DEG_TO_RAD; + } + + + static public final int ceil(float what) { + return (int) Math.ceil(what); + } + + static public final int floor(float what) { + return (int) Math.floor(what); + } + + static public final int round(float what) { + return (int) Math.round(what); + } + + + static public final float mag(float a, float b) { + return (float)Math.sqrt(a*a + b*b); + } + + static public final float mag(float a, float b, float c) { + return (float)Math.sqrt(a*a + b*b + c*c); + } + + + static public final float dist(float x1, float y1, float x2, float y2) { + return sqrt(sq(x2-x1) + sq(y2-y1)); + } + + static public final float dist(float x1, float y1, float z1, + float x2, float y2, float z2) { + return sqrt(sq(x2-x1) + sq(y2-y1) + sq(z2-z1)); + } + + + static public final float lerp(float start, float stop, float amt) { + return start + (stop-start) * amt; + } + + /** + * Normalize a value to exist between 0 and 1 (inclusive). + * Mathematically the opposite of lerp(), figures out what proportion + * a particular value is relative to start and stop coordinates. + */ + static public final float norm(float value, float start, float stop) { + return (value - start) / (stop - start); + } + + /** + * Convenience function to map a variable from one coordinate space + * to another. Equivalent to unlerp() followed by lerp(). + */ + static public final float map(float value, + float istart, float istop, + float ostart, float ostop) { + return ostart + (ostop - ostart) * ((value - istart) / (istop - istart)); + } + + + static public final double map(double value, + double istart, double istop, + double ostart, double ostop) { + return ostart + (ostop - ostart) * ((value - istart) / (istop - istart)); + } + + + + ////////////////////////////////////////////////////////////// + + // RANDOM NUMBERS + + + Random internalRandom; + + /** + * Return a random number in the range [0, howbig). + *

+ * The number returned will range from zero up to + * (but not including) 'howbig'. + */ + public final float random(float howbig) { + // for some reason (rounding error?) Math.random() * 3 + // can sometimes return '3' (once in ~30 million tries) + // so a check was added to avoid the inclusion of 'howbig' + + // avoid an infinite loop + if (howbig == 0) return 0; + + // internal random number object + if (internalRandom == null) internalRandom = new Random(); + + float value = 0; + do { + //value = (float)Math.random() * howbig; + value = internalRandom.nextFloat() * howbig; + } while (value == howbig); + return value; + } + + + /** + * Return a random number in the range [howsmall, howbig). + *

+ * The number returned will range from 'howsmall' up to + * (but not including 'howbig'. + *

+ * If howsmall is >= howbig, howsmall will be returned, + * meaning that random(5, 5) will return 5 (useful) + * and random(7, 4) will return 7 (not useful.. better idea?) + */ + public final float random(float howsmall, float howbig) { + if (howsmall >= howbig) return howsmall; + float diff = howbig - howsmall; + return random(diff) + howsmall; + } + + + public final void randomSeed(long what) { + // internal random number object + if (internalRandom == null) internalRandom = new Random(); + internalRandom.setSeed(what); + } + + + + ////////////////////////////////////////////////////////////// + + // PERLIN NOISE + + // [toxi 040903] + // octaves and amplitude amount per octave are now user controlled + // via the noiseDetail() function. + + // [toxi 030902] + // cleaned up code and now using bagel's cosine table to speed up + + // [toxi 030901] + // implementation by the german demo group farbrausch + // as used in their demo "art": http://www.farb-rausch.de/fr010src.zip + + static final int PERLIN_YWRAPB = 4; + static final int PERLIN_YWRAP = 1<>= 1; + } + + if (x<0) x=-x; + if (y<0) y=-y; + if (z<0) z=-z; + + int xi=(int)x, yi=(int)y, zi=(int)z; + float xf = (float)(x-xi); + float yf = (float)(y-yi); + float zf = (float)(z-zi); + float rxf, ryf; + + float r=0; + float ampl=0.5f; + + float n1,n2,n3; + + for (int i=0; i=1.0f) { xi++; xf--; } + if (yf>=1.0f) { yi++; yf--; } + if (zf>=1.0f) { zi++; zf--; } + } + return r; + } + + // [toxi 031112] + // now adjusts to the size of the cosLUT used via + // the new variables, defined above + private float noise_fsc(float i) { + // using bagel's cosine table instead + return 0.5f*(1.0f-perlin_cosTable[(int)(i*perlin_PI)%perlin_TWOPI]); + } + + // [toxi 040903] + // make perlin noise quality user controlled to allow + // for different levels of detail. lower values will produce + // smoother results as higher octaves are surpressed + + public void noiseDetail(int lod) { + if (lod>0) perlin_octaves=lod; + } + + public void noiseDetail(int lod, float falloff) { + if (lod>0) perlin_octaves=lod; + if (falloff>0) perlin_amp_falloff=falloff; + } + + public void noiseSeed(long what) { + if (perlinRandom == null) perlinRandom = new Random(); + perlinRandom.setSeed(what); + // force table reset after changing the random number seed [0122] + perlin = null; + } + + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + protected String[] loadImageFormats; + + + /** + * Load an image from the data folder or a local directory. + * Supports .gif (including transparency), .tga, and .jpg images. + * In Java 1.3 or later, .png images are + * + * also supported. + *

+ * Generally, loadImage() should only be used during setup, because + * re-loading images inside draw() is likely to cause a significant + * delay while memory is allocated and the thread blocks while waiting + * for the image to load because loading is not asynchronous. + *

+ * To load several images asynchronously, see more information in the + * FAQ about writing your own threaded image loading method. + *

+ * As of 0096, returns null if no image of that name is found, + * rather than an error. + *

+ * Release 0115 also provides support for reading TIFF and RLE-encoded + * Targa (.tga) files written by Processing via save() and saveFrame(). + * Other TIFF and Targa files will probably not load, use a different + * format (gif, jpg and png are safest bets) when creating images with + * another application to use with Processing. + *

+ * Also in release 0115, more image formats (BMP and others) can + * be read when using Java 1.4 and later. Because many people still + * use Java 1.1 and 1.3, these formats are not recommended for + * work that will be posted on the web. To get a list of possible + * image formats for use with Java 1.4 and later, use the following: + * println(javax.imageio.ImageIO.getReaderFormatNames()) + *

+ * Images are loaded via a byte array that is passed to + * Toolkit.createImage(). Unfortunately, we cannot use Applet.getImage() + * because it takes a URL argument, which would be a pain in the a-- + * to make work consistently for online and local sketches. + * Sometimes this causes problems, resulting in issues like + * Bug 279 + * and + * Bug 305. + * In release 0115, everything was instead run through javax.imageio, + * but that turned out to be very slow, see + * Bug 392. + * As a result, starting with 0116, the following happens: + *

+ * For releases 0116 and later, if you have problems such as those seen + * in Bugs 279 and 305, use Applet.getImage() instead. You'll be stuck + * with the limitations of getImage() (the headache of dealing with + * online/offline use). Set up your own MediaTracker, and pass the resulting + * java.awt.Image to the PImage constructor that takes an AWT image. + */ + public PImage loadImage(String filename) { + return loadImage(filename, null); + } + + + /** + * Identical to loadImage, but allows you to specify the type of + * image by its extension. Especially useful when downloading from + * CGI scripts. + *

+ * Use 'unknown' as the extension to pass off to the default + * image loader that handles gif, jpg, and png. + */ + public PImage loadImage(String filename, String extension) { + if (extension == null) { + String lower = filename.toLowerCase(); + int dot = filename.lastIndexOf('.'); + if (dot == -1) { + extension = "unknown"; // no extension found + } + extension = lower.substring(dot + 1); + + // check for, and strip any parameters on the url, i.e. + // filename.jpg?blah=blah&something=that + int question = extension.indexOf('?'); + if (question != -1) { + extension = extension.substring(0, question); + } + } + + // just in case. them users will try anything! + extension = extension.toLowerCase(); + + if (extension.equals("tga")) { + try { + return loadImageTGA(filename); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + if (extension.equals("tif") || extension.equals("tiff")) { + byte bytes[] = loadBytes(filename); + return (bytes == null) ? null : PImage.loadTIFF(bytes); + } + + // For jpeg, gif, and png, load them using createImage(), + // because the javax.imageio code was found to be much slower, see + // Bug 392. + try { + if (extension.equals("jpg") || extension.equals("jpeg") || + extension.equals("gif") || extension.equals("png") || + extension.equals("unknown")) { + byte bytes[] = loadBytes(filename); + if (bytes == null) { + return null; + } else { + Image awtImage = Toolkit.getDefaultToolkit().createImage(bytes); + PImage image = loadImageMT(awtImage); + if (image.width == -1) { + System.err.println("The file " + filename + + " contains bad image data, or may not be an image."); + } + // if it's a .gif image, test to see if it has transparency + if (extension.equals("gif") || extension.equals("png")) { + image.checkAlpha(); + } + return image; + } + } + } catch (Exception e) { + // show error, but move on to the stuff below, see if it'll work + e.printStackTrace(); + } + + if (loadImageFormats == null) { + loadImageFormats = ImageIO.getReaderFormatNames(); + } + if (loadImageFormats != null) { + for (int i = 0; i < loadImageFormats.length; i++) { + if (extension.equals(loadImageFormats[i])) { + return loadImageIO(filename); + } + } + } + + // failed, could not load image after all those attempts + System.err.println("Could not find a method to load " + filename); + return null; + } + + + public PImage requestImage(String filename) { + return requestImage(filename, null); + } + + + public PImage requestImage(String filename, String extension) { + PImage vessel = createImage(0, 0, ARGB); + AsyncImageLoader ail = + new AsyncImageLoader(filename, extension, vessel); + ail.start(); + return vessel; + } + + + /** + * By trial and error, four image loading threads seem to work best when + * loading images from online. This is consistent with the number of open + * connections that web browsers will maintain. The variable is made public + * (however no accessor has been added since it's esoteric) if you really + * want to have control over the value used. For instance, when loading local + * files, it might be better to only have a single thread (or two) loading + * images so that you're disk isn't simply jumping around. + */ + public int requestImageMax = 4; + volatile int requestImageCount; + + class AsyncImageLoader extends Thread { + String filename; + String extension; + PImage vessel; + + public AsyncImageLoader(String filename, String extension, PImage vessel) { + this.filename = filename; + this.extension = extension; + this.vessel = vessel; + } + + public void run() { + while (requestImageCount == requestImageMax) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { } + } + requestImageCount++; + + PImage actual = loadImage(filename, extension); + + // An error message should have already printed + if (actual == null) { + vessel.width = -1; + vessel.height = -1; + + } else { + vessel.width = actual.width; + vessel.height = actual.height; + vessel.format = actual.format; + vessel.pixels = actual.pixels; + } + requestImageCount--; + } + } + + + /** + * Load an AWT image synchronously by setting up a MediaTracker for + * a single image, and blocking until it has loaded. + */ + protected PImage loadImageMT(Image awtImage) { + MediaTracker tracker = new MediaTracker(this); + tracker.addImage(awtImage, 0); + try { + tracker.waitForAll(); + } catch (InterruptedException e) { + //e.printStackTrace(); // non-fatal, right? + } + + PImage image = new PImage(awtImage); + image.parent = this; + return image; + } + + + /** + * Use Java 1.4 ImageIO methods to load an image. + */ + protected PImage loadImageIO(String filename) { + InputStream stream = createInput(filename); + if (stream == null) { + System.err.println("The image " + filename + " could not be found."); + return null; + } + + try { + BufferedImage bi = ImageIO.read(stream); + PImage outgoing = new PImage(bi.getWidth(), bi.getHeight()); + outgoing.parent = this; + + bi.getRGB(0, 0, outgoing.width, outgoing.height, + outgoing.pixels, 0, outgoing.width); + + // check the alpha for this image + // was gonna call getType() on the image to see if RGB or ARGB, + // but it's not actually useful, since gif images will come through + // as TYPE_BYTE_INDEXED, which means it'll still have to check for + // the transparency. also, would have to iterate through all the other + // types and guess whether alpha was in there, so.. just gonna stick + // with the old method. + outgoing.checkAlpha(); + + // return the image + return outgoing; + + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + + /** + * Targa image loader for RLE-compressed TGA files. + *

+ * Rewritten for 0115 to read/write RLE-encoded targa images. + * For 0125, non-RLE encoded images are now supported, along with + * images whose y-order is reversed (which is standard for TGA files). + */ + protected PImage loadImageTGA(String filename) throws IOException { + InputStream is = createInput(filename); + if (is == null) return null; + + byte header[] = new byte[18]; + int offset = 0; + do { + int count = is.read(header, offset, header.length - offset); + if (count == -1) return null; + offset += count; + } while (offset < 18); + + /* + header[2] image type code + 2 (0x02) - Uncompressed, RGB images. + 3 (0x03) - Uncompressed, black and white images. + 10 (0x0A) - Runlength encoded RGB images. + 11 (0x0B) - Compressed, black and white images. (grayscale?) + + header[16] is the bit depth (8, 24, 32) + + header[17] image descriptor (packed bits) + 0x20 is 32 = origin upper-left + 0x28 is 32 + 8 = origin upper-left + 32 bits + + 7 6 5 4 3 2 1 0 + 128 64 32 16 8 4 2 1 + */ + + int format = 0; + + if (((header[2] == 3) || (header[2] == 11)) && // B&W, plus RLE or not + (header[16] == 8) && // 8 bits + ((header[17] == 0x8) || (header[17] == 0x28))) { // origin, 32 bit + format = ALPHA; + + } else if (((header[2] == 2) || (header[2] == 10)) && // RGB, RLE or not + (header[16] == 24) && // 24 bits + ((header[17] == 0x20) || (header[17] == 0))) { // origin + format = RGB; + + } else if (((header[2] == 2) || (header[2] == 10)) && + (header[16] == 32) && + ((header[17] == 0x8) || (header[17] == 0x28))) { // origin, 32 + format = ARGB; + } + + if (format == 0) { + System.err.println("Unknown .tga file format for " + filename); + //" (" + header[2] + " " + + //(header[16] & 0xff) + " " + + //hex(header[17], 2) + ")"); + return null; + } + + int w = ((header[13] & 0xff) << 8) + (header[12] & 0xff); + int h = ((header[15] & 0xff) << 8) + (header[14] & 0xff); + PImage outgoing = createImage(w, h, format); + + // where "reversed" means upper-left corner (normal for most of + // the modernized world, but "reversed" for the tga spec) + boolean reversed = (header[17] & 0x20) != 0; + + if ((header[2] == 2) || (header[2] == 3)) { // not RLE encoded + if (reversed) { + int index = (h-1) * w; + switch (format) { + case ALPHA: + for (int y = h-1; y >= 0; y--) { + for (int x = 0; x < w; x++) { + outgoing.pixels[index + x] = is.read(); + } + index -= w; + } + break; + case RGB: + for (int y = h-1; y >= 0; y--) { + for (int x = 0; x < w; x++) { + outgoing.pixels[index + x] = + is.read() | (is.read() << 8) | (is.read() << 16) | + 0xff000000; + } + index -= w; + } + break; + case ARGB: + for (int y = h-1; y >= 0; y--) { + for (int x = 0; x < w; x++) { + outgoing.pixels[index + x] = + is.read() | (is.read() << 8) | (is.read() << 16) | + (is.read() << 24); + } + index -= w; + } + } + } else { // not reversed + int count = w * h; + switch (format) { + case ALPHA: + for (int i = 0; i < count; i++) { + outgoing.pixels[i] = is.read(); + } + break; + case RGB: + for (int i = 0; i < count; i++) { + outgoing.pixels[i] = + is.read() | (is.read() << 8) | (is.read() << 16) | + 0xff000000; + } + break; + case ARGB: + for (int i = 0; i < count; i++) { + outgoing.pixels[i] = + is.read() | (is.read() << 8) | (is.read() << 16) | + (is.read() << 24); + } + break; + } + } + + } else { // header[2] is 10 or 11 + int index = 0; + int px[] = outgoing.pixels; + + while (index < px.length) { + int num = is.read(); + boolean isRLE = (num & 0x80) != 0; + if (isRLE) { + num -= 127; // (num & 0x7F) + 1 + int pixel = 0; + switch (format) { + case ALPHA: + pixel = is.read(); + break; + case RGB: + pixel = 0xFF000000 | + is.read() | (is.read() << 8) | (is.read() << 16); + //(is.read() << 16) | (is.read() << 8) | is.read(); + break; + case ARGB: + pixel = is.read() | + (is.read() << 8) | (is.read() << 16) | (is.read() << 24); + break; + } + for (int i = 0; i < num; i++) { + px[index++] = pixel; + if (index == px.length) break; + } + } else { // write up to 127 bytes as uncompressed + num += 1; + switch (format) { + case ALPHA: + for (int i = 0; i < num; i++) { + px[index++] = is.read(); + } + break; + case RGB: + for (int i = 0; i < num; i++) { + px[index++] = 0xFF000000 | + is.read() | (is.read() << 8) | (is.read() << 16); + //(is.read() << 16) | (is.read() << 8) | is.read(); + } + break; + case ARGB: + for (int i = 0; i < num; i++) { + px[index++] = is.read() | //(is.read() << 24) | + (is.read() << 8) | (is.read() << 16) | (is.read() << 24); + //(is.read() << 16) | (is.read() << 8) | is.read(); + } + break; + } + } + } + + if (!reversed) { + int[] temp = new int[w]; + for (int y = 0; y < h/2; y++) { + int z = (h-1) - y; + System.arraycopy(px, y*w, temp, 0, w); + System.arraycopy(px, z*w, px, y*w, w); + System.arraycopy(temp, 0, px, z*w, w); + } + } + } + + return outgoing; + } + + + + ////////////////////////////////////////////////////////////// + + // SHAPE I/O + + + /** + * Load a geometry from a file as a PShape. Currently only supports SVG data. + */ + public PShape loadShape(String filename) { + if (filename.toLowerCase().endsWith(".svg")) { + return new PShapeSVG(this, filename); + } + return null; + } + + + + ////////////////////////////////////////////////////////////// + + // FONT I/O + + + public PFont loadFont(String filename) { + try { + InputStream input = createInput(filename); + return new PFont(input); + + } catch (Exception e) { + die("Could not load font " + filename + ". " + + "Make sure that the font has been copied " + + "to the data folder of your sketch.", e); + } + return null; + } + + + public PFont createFont(String name, float size) { + return createFont(name, size, true, PFont.DEFAULT_CHARSET); + } + + + public PFont createFont(String name, float size, boolean smooth) { + return createFont(name, size, smooth, PFont.DEFAULT_CHARSET); + } + + + /** + * Create a .vlw font on the fly from either a font name that's + * installed on the system, or from a .ttf or .otf that's inside + * the data folder of this sketch. + *

+ * Only works with Java 1.3 or later. Many .otf fonts don't seem + * to be supported by Java, perhaps because they're CFF based? + *

+ * Font names are inconsistent across platforms and Java versions. + * On Mac OS X, Java 1.3 uses the font menu name of the font, + * whereas Java 1.4 uses the PostScript name of the font. Java 1.4 + * on OS X will also accept the font menu name as well. On Windows, + * it appears that only the menu names are used, no matter what + * Java version is in use. Naming system unknown/untested for 1.5. + *

+ * Use 'null' for the charset if you want to use any of the 65,536 + * unicode characters that exist in the font. Note that this can + * produce an enormous file or may cause an OutOfMemoryError. + */ + public PFont createFont(String name, float size, + boolean smooth, char charset[]) { + String lowerName = name.toLowerCase(); + Font baseFont = null; + + try { + if (lowerName.endsWith(".otf") || lowerName.endsWith(".ttf")) { + InputStream stream = createInput(name); + if (stream == null) { + System.err.println("The font \"" + name + "\" " + + "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; + } + baseFont = Font.createFont(Font.TRUETYPE_FONT, createInput(name)); + + } else { + //baseFont = new Font(name, Font.PLAIN, 1); + baseFont = PFont.findFont(name); + } + } catch (Exception e) { + System.err.println("Problem using createFont() with " + name); + e.printStackTrace(); + } + return new PFont(baseFont.deriveFont(size), smooth, charset); + } + + + + ////////////////////////////////////////////////////////////// + + // FILE/FOLDER SELECTION + + + public File selectedFile; + protected Frame parentFrame; + + + protected void checkParentFrame() { + if (parentFrame == null) { + Component comp = getParent(); + while (comp != null) { + if (comp instanceof Frame) { + parentFrame = (Frame) comp; + break; + } + comp = comp.getParent(); + } + // Who you callin' a hack? + if (parentFrame == null) { + parentFrame = new Frame(); + } + } + } + + + /** + * Open a platform-specific file chooser dialog to select a file for input. + * @return full path to the selected file, or null if no selection. + */ + public String selectInput() { + return selectInput("Select a file..."); + } + + + /** + * Open a platform-specific file chooser dialog to select a file for input. + * @param prompt Mesage to show the user when prompting for a file. + * @return full path to the selected file, or null if canceled. + */ + public String selectInput(String prompt) { + return selectFileImpl(prompt, FileDialog.LOAD); + } + + + /** + * Open a platform-specific file save dialog to select a file for output. + * @return full path to the file entered, or null if canceled. + */ + public String selectOutput() { + return selectOutput("Save as..."); + } + + + /** + * Open a platform-specific file save dialog to select a file for output. + * @param prompt Mesage to show the user when prompting for a file. + * @return full path to the file entered, or null if canceled. + */ + public String selectOutput(String prompt) { + return selectFileImpl(prompt, FileDialog.SAVE); + } + + + protected String selectFileImpl(final String prompt, final int mode) { + checkParentFrame(); + + try { + SwingUtilities.invokeAndWait(new Runnable() { + public void run() { + FileDialog fileDialog = + new FileDialog(parentFrame, prompt, mode); + fileDialog.setVisible(true); + String directory = fileDialog.getDirectory(); + String filename = fileDialog.getFile(); + selectedFile = + (filename == null) ? null : new File(directory, filename); + } + }); + return (selectedFile == null) ? null : selectedFile.getAbsolutePath(); + + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + + /** + * Open a platform-specific folder chooser dialog. + * @return full path to the selected folder, or null if no selection. + */ + public String selectFolder() { + return selectFolder("Select a folder..."); + } + + + /** + * Open a platform-specific folder chooser dialog. + * @param prompt Mesage to show the user when prompting for a file. + * @return full path to the selected folder, or null if no selection. + */ + public String selectFolder(final String prompt) { + checkParentFrame(); + + try { + SwingUtilities.invokeAndWait(new Runnable() { + public void run() { + if (platform == MACOSX) { + FileDialog fileDialog = + new FileDialog(parentFrame, prompt, FileDialog.LOAD); + System.setProperty("apple.awt.fileDialogForDirectories", "true"); + fileDialog.setVisible(true); + System.setProperty("apple.awt.fileDialogForDirectories", "false"); + String filename = fileDialog.getFile(); + selectedFile = (filename == null) ? null : + new File(fileDialog.getDirectory(), fileDialog.getFile()); + } else { + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setDialogTitle(prompt); + fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + + int returned = fileChooser.showOpenDialog(parentFrame); + System.out.println(returned); + if (returned == JFileChooser.CANCEL_OPTION) { + selectedFile = null; + } else { + selectedFile = fileChooser.getSelectedFile(); + } + } + } + }); + return (selectedFile == null) ? null : selectedFile.getAbsolutePath(); + + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + + + ////////////////////////////////////////////////////////////// + + // READERS AND WRITERS + + + /** + * I want to read lines from a file. I have RSI from typing these + * eight lines of code so many times. + */ + public BufferedReader createReader(String filename) { + try { + InputStream is = createInput(filename); + if (is == null) { + System.err.println(filename + " does not exist or could not be read"); + return null; + } + return createReader(is); + + } catch (Exception e) { + if (filename == null) { + System.err.println("Filename passed to reader() was null"); + } else { + System.err.println("Couldn't create a reader for " + filename); + } + } + return null; + } + + + /** + * I want to read lines from a file. And I'm still annoyed. + */ + 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 (Exception e) { + if (file == null) { + throw new RuntimeException("File passed to createReader() was null"); + } else { + e.printStackTrace(); + throw new RuntimeException("Couldn't create a reader for " + + file.getAbsolutePath()); + } + } + //return null; + } + + + /** + * I want to read lines from a stream. If I have to type the + * following lines any more I'm gonna send Sun my medical bills. + */ + static public BufferedReader createReader(InputStream input) { + InputStreamReader isr = null; + try { + isr = new InputStreamReader(input, "UTF-8"); + } catch (UnsupportedEncodingException e) { } // not gonna happen + return new BufferedReader(isr); + } + + + /** + * I want to print lines to a file. Why can't I? + */ + public PrintWriter createWriter(String filename) { + return createWriter(saveFile(filename)); + } + + + /** + * 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) { + 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) { + if (file == null) { + throw new RuntimeException("File passed to createWriter() was null"); + } else { + e.printStackTrace(); + throw new RuntimeException("Couldn't create a writer for " + + file.getAbsolutePath()); + } + } + //return null; + } + + + /** + * 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) { + try { + OutputStreamWriter osw = new OutputStreamWriter(output, "UTF-8"); + return new PrintWriter(osw); + } catch (UnsupportedEncodingException e) { } // not gonna happen + return null; + } + + + ////////////////////////////////////////////////////////////// + + // FILE INPUT + + + /** + * @deprecated As of release 0136, use createInput() instead. + */ + public InputStream openStream(String filename) { + return createInput(filename); + } + + + /** + * 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 applet) + *

+ * For 0096+, the "data" folder is exported intact with subfolders, + * and openStream() properly handles subdirectories from the data folder + *

+ * If not online, this will also check to see if the user is asking + * for a file whose name isn't properly capitalized. This helps prevent + * issues when a sketch is exported to the web, where case sensitivity + * matters, as opposed to Windows and the Mac OS default where + * case sensitivity is preserved but ignored. + *

+ * It is strongly recommended that libraries use this method to open + * data files, so that the loading sequence is handled in the same way + * as functions like loadBytes(), loadImage(), etc. + *

+ * The filename passed in can be: + *

+ */ + public InputStream createInput(String filename) { + InputStream input = createInputRaw(filename); + if ((input != null) && filename.toLowerCase().endsWith(".gz")) { + try { + return new GZIPInputStream(input); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + return input; + } + + + /** + * Call openStream() without automatic gzip decompression. + */ + public InputStream createInputRaw(String filename) { + InputStream stream = null; + + if (filename == null) return null; + + if (filename.length() == 0) { + // an error will be called by the parent function + //System.err.println("The filename passed to openStream() was empty."); + return null; + } + + // safe to check for this as a url first. this will prevent online + // access logs from being spammed with GET /sketchfolder/http://blahblah + try { + URL url = new URL(filename); + stream = url.openStream(); + return stream; + + } catch (MalformedURLException mfue) { + // not a url, that's fine + + } catch (FileNotFoundException fnfe) { + // Java 1.5 likes to throw this when URL not available. (fix for 0119) + // http://dev.processing.org/bugs/show_bug.cgi?id=403 + + } catch (IOException e) { + // changed for 0117, shouldn't be throwing exception + e.printStackTrace(); + //System.err.println("Error downloading from URL " + filename); + return null; + //throw new RuntimeException("Error downloading from URL " + filename); + } + + // Moved this earlier than the getResourceAsStream() checks, because + // calling getResourceAsStream() on a directory lists its contents. + // http://dev.processing.org/bugs/show_bug.cgi?id=716 + try { + // First see if it's in a data folder. This may fail by throwing + // a SecurityException. If so, this whole block will be skipped. + File file = new File(dataPath(filename)); + if (!file.exists()) { + // next see if it's just in the sketch folder + file = new File(sketchPath, filename); + } + if (file.isDirectory()) { + return null; + } + if (file.exists()) { + try { + // handle case sensitivity check + String filePath = file.getCanonicalPath(); + String filenameActual = new File(filePath).getName(); + // make sure there isn't a subfolder prepended to the name + String filenameShort = new File(filename).getName(); + // if the actual filename is the same, but capitalized + // differently, warn the user. + //if (filenameActual.equalsIgnoreCase(filenameShort) && + //!filenameActual.equals(filenameShort)) { + if (!filenameActual.equals(filenameShort)) { + throw new RuntimeException("This file is named " + + filenameActual + " not " + + filename + ". Rename the file " + + "or change your code."); + } + } catch (IOException e) { } + } + + // if this file is ok, may as well just load it + stream = new FileInputStream(file); + if (stream != null) return stream; + + // have to break these out because a general Exception might + // catch the RuntimeException being thrown above + } catch (IOException ioe) { + } catch (SecurityException se) { } + + // Using getClassLoader() prevents java from converting dots + // to slashes or requiring a slash at the beginning. + // (a slash as a prefix means that it'll load from the root of + // the jar, rather than trying to dig into the package location) + ClassLoader cl = getClass().getClassLoader(); + + // by default, data files are exported to the root path of the jar. + // (not the data folder) so check there first. + stream = cl.getResourceAsStream("data/" + filename); + if (stream != null) { + String cn = stream.getClass().getName(); + // this is an irritation of sun's java plug-in, which will return + // a non-null stream for an object that doesn't exist. like all good + // things, this is probably introduced in java 1.5. awesome! + // http://dev.processing.org/bugs/show_bug.cgi?id=359 + if (!cn.equals("sun.plugin.cache.EmptyInputStream")) { + return stream; + } + } + + // When used with an online script, also need to check without the + // data folder, in case it's not in a subfolder called 'data'. + // http://dev.processing.org/bugs/show_bug.cgi?id=389 + stream = cl.getResourceAsStream(filename); + if (stream != null) { + String cn = stream.getClass().getName(); + if (!cn.equals("sun.plugin.cache.EmptyInputStream")) { + return stream; + } + } + + try { + // attempt to load from a local file, used when running as + // an application, or as a signed applet + try { // first try to catch any security exceptions + try { + stream = new FileInputStream(dataPath(filename)); + if (stream != null) return stream; + } catch (IOException e2) { } + + try { + stream = new FileInputStream(sketchPath(filename)); + if (stream != null) return stream; + } catch (Exception e) { } // ignored + + try { + stream = new FileInputStream(filename); + if (stream != null) return stream; + } catch (IOException e1) { } + + } catch (SecurityException se) { } // online, whups + + } catch (Exception e) { + //die(e.getMessage(), e); + e.printStackTrace(); + } + return null; + } + + + static public InputStream createInput(File file) { + try { + InputStream input = new FileInputStream(file); + if (file.getName().toLowerCase().endsWith(".gz")) { + return new GZIPInputStream(input); + } + return input; + + } catch (IOException e) { + if (file == null) { + throw new RuntimeException("File passed to openStream() was null"); + + } else { + e.printStackTrace(); + throw new RuntimeException("Couldn't openStream() for " + + file.getAbsolutePath()); + } + } + } + + + public byte[] loadBytes(String filename) { + InputStream is = createInput(filename); + if (is != null) return loadBytes(is); + + System.err.println("The file \"" + filename + "\" " + + "is missing or inaccessible, make sure " + + "the URL is valid or that the file has been " + + "added to your sketch and is readable."); + return null; + } + + + static public byte[] loadBytes(InputStream input) { + try { + BufferedInputStream bis = new BufferedInputStream(input); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + int c = bis.read(); + while (c != -1) { + out.write(c); + c = bis.read(); + } + return out.toByteArray(); + + } catch (IOException e) { + e.printStackTrace(); + //throw new RuntimeException("Couldn't load bytes from stream"); + } + return null; + } + + + static public byte[] loadBytes(File file) { + InputStream is = createInput(file); + return loadBytes(is); + } + + + static public String[] loadStrings(File file) { + InputStream is = createInput(file); + if (is != null) return loadStrings(is); + return null; + } + + + /** + * Load data from a file and shove it into a String array. + *

+ * Exceptions are handled internally, when an error, occurs, an + * exception is printed to the console and 'null' is returned, + * but the program continues running. This is a tradeoff between + * 1) showing the user that there was a problem but 2) not requiring + * that all i/o code is contained in try/catch blocks, for the sake + * of new users (or people who are just trying to get things done + * in a "scripting" fashion. If you want to handle exceptions, + * use Java methods for I/O. + */ + public String[] loadStrings(String filename) { + InputStream is = createInput(filename); + if (is != null) return loadStrings(is); + + System.err.println("The file \"" + filename + "\" " + + "is missing or inaccessible, make sure " + + "the URL is valid or that the file has been " + + "added to your sketch and is readable."); + return null; + } + + + static public String[] loadStrings(InputStream input) { + try { + BufferedReader reader = + new BufferedReader(new InputStreamReader(input, "UTF-8")); + + String lines[] = new String[100]; + int lineCount = 0; + String line = null; + while ((line = reader.readLine()) != null) { + if (lineCount == lines.length) { + String temp[] = new String[lineCount << 1]; + System.arraycopy(lines, 0, temp, 0, lineCount); + lines = temp; + } + lines[lineCount++] = line; + } + reader.close(); + + if (lineCount == lines.length) { + return lines; + } + + // resize array to appropriate amount for these lines + String output[] = new String[lineCount]; + System.arraycopy(lines, 0, output, 0, lineCount); + return output; + + } catch (IOException e) { + e.printStackTrace(); + //throw new RuntimeException("Error inside loadStrings()"); + } + return null; + } + + + + ////////////////////////////////////////////////////////////// + + // FILE OUTPUT + + + /** + * Similar to createInput() (formerly openStream), this creates a Java + * OutputStream for a given filename or path. The file will be created in + * the sketch folder, or in the same folder as an exported application. + *

+ * If the path does not exist, intermediate folders will be created. If an + * exception occurs, it will be printed to the console, and null will be + * returned. + *

+ * Future releases may also add support for handling HTTP POST via this + * method (for better symmetry with createInput), however that's maybe a + * little too clever (and then we'd have to add the same features to the + * other file functions like createWriter). Who you callin' bloated? + */ + public OutputStream createOutput(String filename) { + return createOutput(saveFile(filename)); + } + + + static public OutputStream createOutput(File file) { + try { + createPath(file); // make sure the path exists + FileOutputStream fos = new FileOutputStream(file); + if (file.getName().toLowerCase().endsWith(".gz")) { + return new GZIPOutputStream(fos); + } + return fos; + + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + + /** + * Save the contents of a stream to a file in the sketch folder. + * This is basically saveBytes(blah, loadBytes()), but done + * more efficiently (and with less confusing syntax). + */ + public void saveStream(String targetFilename, String sourceLocation) { + saveStream(saveFile(targetFilename), sourceLocation); + } + + + /** + * Identical to the other saveStream(), but writes to a File + * object, for greater control over the file location. + * Note that unlike other api methods, this will not automatically + * compress or uncompress gzip files. + */ + public void saveStream(File targetFile, String sourceLocation) { + saveStream(targetFile, createInputRaw(sourceLocation)); + } + + + static public void saveStream(File targetFile, InputStream sourceStream) { + File tempFile = null; + try { + File parentDir = targetFile.getParentFile(); + tempFile = File.createTempFile(targetFile.getName(), null, parentDir); + + BufferedInputStream bis = new BufferedInputStream(sourceStream, 16384); + FileOutputStream fos = new FileOutputStream(tempFile); + BufferedOutputStream bos = new BufferedOutputStream(fos); + + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = bis.read(buffer)) != -1) { + bos.write(buffer, 0, bytesRead); + } + + bos.flush(); + bos.close(); + bos = null; + + if (!tempFile.renameTo(targetFile)) { + System.err.println("Could not rename temporary file " + + tempFile.getAbsolutePath()); + } + } catch (IOException e) { + if (tempFile != null) { + tempFile.delete(); + } + e.printStackTrace(); + } + } + + + /** + * Saves bytes to a file to inside the sketch folder. + * The filename can be a relative path, i.e. "poo/bytefun.txt" + * would save to a file named "bytefun.txt" to a subfolder + * called 'poo' inside the sketch folder. If the in-between + * subfolders don't exist, they'll be created. + */ + public void saveBytes(String filename, byte buffer[]) { + saveBytes(saveFile(filename), buffer); + } + + + /** + * Saves bytes to a specific File location specified by the user. + */ + static public void saveBytes(File file, byte buffer[]) { + try { + String filename = file.getAbsolutePath(); + createPath(filename); + OutputStream output = new FileOutputStream(file); + if (file.getName().toLowerCase().endsWith(".gz")) { + output = new GZIPOutputStream(output); + } + saveBytes(output, buffer); + output.close(); + + } catch (IOException e) { + System.err.println("error saving bytes to " + file); + e.printStackTrace(); + } + } + + + /** + * Spews a buffer of bytes to an OutputStream. + */ + static public void saveBytes(OutputStream output, byte buffer[]) { + try { + output.write(buffer); + output.flush(); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + // + + public void saveStrings(String filename, String strings[]) { + saveStrings(saveFile(filename), strings); + } + + + static public void saveStrings(File file, String strings[]) { + try { + String location = file.getAbsolutePath(); + createPath(location); + OutputStream output = new FileOutputStream(location); + if (file.getName().toLowerCase().endsWith(".gz")) { + output = new GZIPOutputStream(output); + } + saveStrings(output, strings); + output.close(); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + + static public void saveStrings(OutputStream output, String strings[]) { + try { + OutputStreamWriter osw = new OutputStreamWriter(output, "UTF-8"); + PrintWriter writer = new PrintWriter(osw); + for (int i = 0; i < strings.length; i++) { + writer.println(strings[i]); + } + writer.flush(); + } catch (UnsupportedEncodingException e) { } // will not happen + } + + + ////////////////////////////////////////////////////////////// + + + /** + * Prepend the sketch folder path to the filename (or path) that is + * passed in. External libraries should use this function to save to + * the sketch folder. + *

+ * Note that when running as an applet inside a web browser, + * the sketchPath will be set to null, because security restrictions + * prevent applets from accessing that information. + *

+ * This will also cause an error if the sketch is not inited properly, + * meaning that init() was never called on the PApplet when hosted + * my some other main() or by other code. For proper use of init(), + * see the examples in the main description text for PApplet. + */ + public String sketchPath(String where) { + if (sketchPath == null) { + return where; +// throw new RuntimeException("The applet was not inited properly, " + +// "or security restrictions prevented " + +// "it from determining its path."); + } + // isAbsolute() could throw an access exception, but so will writing + // to the local disk using the sketch path, so this is safe here. + // for 0120, added a try/catch anyways. + try { + if (new File(where).isAbsolute()) return where; + } catch (Exception e) { } + + return sketchPath + File.separator + where; + } + + + public File sketchFile(String where) { + return new File(sketchPath(where)); + } + + + /** + * Returns a path inside the applet folder to save to. Like sketchPath(), + * but creates any in-between folders so that things save properly. + *

+ * All saveXxxx() functions use the path to the sketch folder, rather than + * its data folder. Once exported, the data folder will be found inside the + * jar file of the exported application or applet. In this case, it's not + * possible to save data into the jar file, because it will often be running + * from a server, or marked in-use if running from a local file system. + * With this in mind, saving to the data path doesn't make sense anyway. + * If you know you're running locally, and want to save to the data folder, + * use saveXxxx("data/blah.dat"). + */ + public String savePath(String where) { + if (where == null) return null; + String filename = sketchPath(where); + createPath(filename); + return filename; + } + + + /** + * Identical to savePath(), but returns a File object. + */ + public File saveFile(String where) { + return new File(savePath(where)); + } + + + /** + * Return a full path to an item in the data folder. + *

+ * In this method, the data path is defined not as the applet's actual + * data path, but a folder titled "data" in the sketch's working + * directory. When running inside the PDE, this will be the sketch's + * "data" folder. However, when exported (as application or applet), + * sketch's data folder is exported as part of the applications jar file, + * and it's not possible to read/write from the jar file in a generic way. + * If you need to read data from the jar file, you should use other methods + * such as createInput(), createReader(), or loadStrings(). + */ + public String dataPath(String where) { + // isAbsolute() could throw an access exception, but so will writing + // to the local disk using the sketch path, so this is safe here. + if (new File(where).isAbsolute()) return where; + + return sketchPath + File.separator + "data" + File.separator + where; + } + + + /** + * Return a full path to an item in the data folder as a File object. + * See the dataPath() method for more information. + */ + public File dataFile(String where) { + return new File(dataPath(where)); + } + + + /** + * Takes a path and creates any in-between folders if they don't + * already exist. Useful when trying to save to a subfolder that + * may not actually exist. + */ + static public void createPath(String path) { + createPath(new File(path)); + } + + + static public void createPath(File file) { + try { + String parent = file.getParent(); + if (parent != null) { + File unit = new File(parent); + if (!unit.exists()) unit.mkdirs(); + } + } catch (SecurityException se) { + System.err.println("You don't have permissions to create " + + file.getAbsolutePath()); + } + } + + + + ////////////////////////////////////////////////////////////// + + // SORT + + + static public byte[] sort(byte what[]) { + return sort(what, what.length); + } + + + static public byte[] sort(byte[] what, int count) { + byte[] outgoing = new byte[what.length]; + System.arraycopy(what, 0, outgoing, 0, what.length); + Arrays.sort(outgoing, 0, count); + return outgoing; + } + + + static public char[] sort(char what[]) { + return sort(what, what.length); + } + + + static public char[] sort(char[] what, int count) { + char[] outgoing = new char[what.length]; + System.arraycopy(what, 0, outgoing, 0, what.length); + Arrays.sort(outgoing, 0, count); + return outgoing; + } + + + static public int[] sort(int what[]) { + return sort(what, what.length); + } + + + static public int[] sort(int[] what, int count) { + int[] outgoing = new int[what.length]; + System.arraycopy(what, 0, outgoing, 0, what.length); + Arrays.sort(outgoing, 0, count); + return outgoing; + } + + + static public float[] sort(float what[]) { + return sort(what, what.length); + } + + + static public float[] sort(float[] what, int count) { + float[] outgoing = new float[what.length]; + System.arraycopy(what, 0, outgoing, 0, what.length); + Arrays.sort(outgoing, 0, count); + return outgoing; + } + + + static public String[] sort(String what[]) { + return sort(what, what.length); + } + + + static public String[] sort(String[] what, int count) { + String[] outgoing = new String[what.length]; + System.arraycopy(what, 0, outgoing, 0, what.length); + Arrays.sort(outgoing, 0, count); + return outgoing; + } + + + + ////////////////////////////////////////////////////////////// + + // ARRAY UTILITIES + + + /** + * Calls System.arraycopy(), included here so that we can + * avoid people needing to learn about the System object + * before they can just copy an array. + */ + static public void arrayCopy(Object src, int srcPosition, + Object dst, int dstPosition, + int length) { + System.arraycopy(src, srcPosition, dst, dstPosition, length); + } + + + /** + * Convenience method for arraycopy(). + * Identical to arraycopy(src, 0, dst, 0, length); + */ + static public void arrayCopy(Object src, Object dst, int length) { + System.arraycopy(src, 0, dst, 0, length); + } + + + /** + * Shortcut to copy the entire contents of + * the source into the destination array. + * Identical to arraycopy(src, 0, dst, 0, src.length); + */ + static public void arrayCopy(Object src, Object dst) { + System.arraycopy(src, 0, dst, 0, Array.getLength(src)); + } + + // + + /** + * @deprecated Use arrayCopy() instead. + */ + static public void arraycopy(Object src, int srcPosition, + Object dst, int dstPosition, + int length) { + System.arraycopy(src, srcPosition, dst, dstPosition, length); + } + + /** + * @deprecated Use arrayCopy() instead. + */ + static public void arraycopy(Object src, Object dst, int length) { + System.arraycopy(src, 0, dst, 0, length); + } + + /** + * @deprecated Use arrayCopy() instead. + */ + static public void arraycopy(Object src, Object dst) { + System.arraycopy(src, 0, dst, 0, Array.getLength(src)); + } + + // + + static public boolean[] expand(boolean list[]) { + return expand(list, list.length << 1); + } + + static public boolean[] expand(boolean list[], int newSize) { + boolean temp[] = new boolean[newSize]; + System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length)); + return temp; + } + + + static public byte[] expand(byte list[]) { + return expand(list, list.length << 1); + } + + static public byte[] expand(byte list[], int newSize) { + byte temp[] = new byte[newSize]; + System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length)); + return temp; + } + + + static public char[] expand(char list[]) { + return expand(list, list.length << 1); + } + + static public char[] expand(char list[], int newSize) { + char temp[] = new char[newSize]; + System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length)); + return temp; + } + + + static public int[] expand(int list[]) { + return expand(list, list.length << 1); + } + + static public int[] expand(int list[], int newSize) { + int temp[] = new int[newSize]; + System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length)); + return temp; + } + + + static public float[] expand(float list[]) { + return expand(list, list.length << 1); + } + + static public float[] expand(float list[], int newSize) { + float temp[] = new float[newSize]; + System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length)); + return temp; + } + + + static public String[] expand(String list[]) { + return expand(list, list.length << 1); + } + + static public String[] expand(String list[], int newSize) { + String temp[] = new String[newSize]; + // in case the new size is smaller than list.length + System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length)); + return temp; + } + + + static public Object expand(Object array) { + return expand(array, Array.getLength(array) << 1); + } + + static public Object expand(Object list, int newSize) { + Class type = list.getClass().getComponentType(); + Object temp = Array.newInstance(type, newSize); + System.arraycopy(list, 0, temp, 0, + Math.min(Array.getLength(list), newSize)); + return temp; + } + + // + + // contract() has been removed in revision 0124, use subset() instead. + // (expand() is also functionally equivalent) + + // + + static public byte[] append(byte b[], byte value) { + b = expand(b, b.length + 1); + b[b.length-1] = value; + return b; + } + + static public char[] append(char b[], char value) { + b = expand(b, b.length + 1); + b[b.length-1] = value; + return b; + } + + static public int[] append(int b[], int value) { + b = expand(b, b.length + 1); + b[b.length-1] = value; + return b; + } + + static public float[] append(float b[], float value) { + b = expand(b, b.length + 1); + b[b.length-1] = value; + return b; + } + + static public String[] append(String b[], String value) { + b = expand(b, b.length + 1); + b[b.length-1] = value; + return b; + } + + static public Object append(Object b, Object value) { + int length = Array.getLength(b); + b = expand(b, length + 1); + Array.set(b, length, value); + return b; + } + + // + + static public boolean[] shorten(boolean list[]) { + return subset(list, 0, list.length-1); + } + + static public byte[] shorten(byte list[]) { + return subset(list, 0, list.length-1); + } + + static public char[] shorten(char list[]) { + return subset(list, 0, list.length-1); + } + + static public int[] shorten(int list[]) { + return subset(list, 0, list.length-1); + } + + static public float[] shorten(float list[]) { + return subset(list, 0, list.length-1); + } + + static public String[] shorten(String list[]) { + return subset(list, 0, list.length-1); + } + + static public Object shorten(Object list) { + int length = Array.getLength(list); + return subset(list, 0, length - 1); + } + + // + + static final public boolean[] splice(boolean list[], + boolean v, int index) { + boolean outgoing[] = new boolean[list.length + 1]; + System.arraycopy(list, 0, outgoing, 0, index); + outgoing[index] = v; + System.arraycopy(list, index, outgoing, index + 1, + list.length - index); + return outgoing; + } + + static final public boolean[] splice(boolean list[], + boolean v[], int index) { + boolean outgoing[] = new boolean[list.length + v.length]; + System.arraycopy(list, 0, outgoing, 0, index); + System.arraycopy(v, 0, outgoing, index, v.length); + System.arraycopy(list, index, outgoing, index + v.length, + list.length - index); + return outgoing; + } + + + static final public byte[] splice(byte list[], + byte v, int index) { + byte outgoing[] = new byte[list.length + 1]; + System.arraycopy(list, 0, outgoing, 0, index); + outgoing[index] = v; + System.arraycopy(list, index, outgoing, index + 1, + list.length - index); + return outgoing; + } + + static final public byte[] splice(byte list[], + byte v[], int index) { + byte outgoing[] = new byte[list.length + v.length]; + System.arraycopy(list, 0, outgoing, 0, index); + System.arraycopy(v, 0, outgoing, index, v.length); + System.arraycopy(list, index, outgoing, index + v.length, + list.length - index); + return outgoing; + } + + + static final public char[] splice(char list[], + char v, int index) { + char outgoing[] = new char[list.length + 1]; + System.arraycopy(list, 0, outgoing, 0, index); + outgoing[index] = v; + System.arraycopy(list, index, outgoing, index + 1, + list.length - index); + return outgoing; + } + + static final public char[] splice(char list[], + char v[], int index) { + char outgoing[] = new char[list.length + v.length]; + System.arraycopy(list, 0, outgoing, 0, index); + System.arraycopy(v, 0, outgoing, index, v.length); + System.arraycopy(list, index, outgoing, index + v.length, + list.length - index); + return outgoing; + } + + + static final public int[] splice(int list[], + int v, int index) { + int outgoing[] = new int[list.length + 1]; + System.arraycopy(list, 0, outgoing, 0, index); + outgoing[index] = v; + System.arraycopy(list, index, outgoing, index + 1, + list.length - index); + return outgoing; + } + + static final public int[] splice(int list[], + int v[], int index) { + int outgoing[] = new int[list.length + v.length]; + System.arraycopy(list, 0, outgoing, 0, index); + System.arraycopy(v, 0, outgoing, index, v.length); + System.arraycopy(list, index, outgoing, index + v.length, + list.length - index); + return outgoing; + } + + + static final public float[] splice(float list[], + float v, int index) { + float outgoing[] = new float[list.length + 1]; + System.arraycopy(list, 0, outgoing, 0, index); + outgoing[index] = v; + System.arraycopy(list, index, outgoing, index + 1, + list.length - index); + return outgoing; + } + + static final public float[] splice(float list[], + float v[], int index) { + float outgoing[] = new float[list.length + v.length]; + System.arraycopy(list, 0, outgoing, 0, index); + System.arraycopy(v, 0, outgoing, index, v.length); + System.arraycopy(list, index, outgoing, index + v.length, + list.length - index); + return outgoing; + } + + + static final public String[] splice(String list[], + String v, int index) { + String outgoing[] = new String[list.length + 1]; + System.arraycopy(list, 0, outgoing, 0, index); + outgoing[index] = v; + System.arraycopy(list, index, outgoing, index + 1, + list.length - index); + return outgoing; + } + + static final public String[] splice(String list[], + String v[], int index) { + String outgoing[] = new String[list.length + v.length]; + System.arraycopy(list, 0, outgoing, 0, index); + System.arraycopy(v, 0, outgoing, index, v.length); + System.arraycopy(list, index, outgoing, index + v.length, + list.length - index); + return outgoing; + } + + + static final public Object splice(Object list, Object v, int index) { + Object[] outgoing = null; + int length = Array.getLength(list); + + // check whether item being spliced in is an array + if (v.getClass().getName().charAt(0) == '[') { + int vlength = Array.getLength(v); + outgoing = new Object[length + vlength]; + System.arraycopy(list, 0, outgoing, 0, index); + System.arraycopy(v, 0, outgoing, index, vlength); + System.arraycopy(list, index, outgoing, index + vlength, length - index); + + } else { + outgoing = new Object[length + 1]; + System.arraycopy(list, 0, outgoing, 0, index); + Array.set(outgoing, index, v); + System.arraycopy(list, index, outgoing, index + 1, length - index); + } + return outgoing; + } + + // + + static public boolean[] subset(boolean list[], int start) { + return subset(list, start, list.length - start); + } + + static public boolean[] subset(boolean list[], int start, int count) { + boolean output[] = new boolean[count]; + System.arraycopy(list, start, output, 0, count); + return output; + } + + + static public byte[] subset(byte list[], int start) { + return subset(list, start, list.length - start); + } + + static public byte[] subset(byte list[], int start, int count) { + byte output[] = new byte[count]; + System.arraycopy(list, start, output, 0, count); + return output; + } + + + static public char[] subset(char list[], int start) { + return subset(list, start, list.length - start); + } + + static public char[] subset(char list[], int start, int count) { + char output[] = new char[count]; + System.arraycopy(list, start, output, 0, count); + return output; + } + + + static public int[] subset(int list[], int start) { + return subset(list, start, list.length - start); + } + + static public int[] subset(int list[], int start, int count) { + int output[] = new int[count]; + System.arraycopy(list, start, output, 0, count); + return output; + } + + + static public float[] subset(float list[], int start) { + return subset(list, start, list.length - start); + } + + static public float[] subset(float list[], int start, int count) { + float output[] = new float[count]; + System.arraycopy(list, start, output, 0, count); + return output; + } + + + static public String[] subset(String list[], int start) { + return subset(list, start, list.length - start); + } + + static public String[] subset(String list[], int start, int count) { + String output[] = new String[count]; + System.arraycopy(list, start, output, 0, count); + return output; + } + + + static public Object subset(Object list, int start) { + int length = Array.getLength(list); + return subset(list, start, length - start); + } + + static public Object subset(Object list, int start, int count) { + Class type = list.getClass().getComponentType(); + Object outgoing = Array.newInstance(type, count); + System.arraycopy(list, start, outgoing, 0, count); + return outgoing; + } + + // + + static public boolean[] concat(boolean a[], boolean b[]) { + boolean c[] = new boolean[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } + + static public byte[] concat(byte a[], byte b[]) { + byte c[] = new byte[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } + + static public char[] concat(char a[], char b[]) { + char c[] = new char[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } + + static public int[] concat(int a[], int b[]) { + int c[] = new int[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } + + static public float[] concat(float a[], float b[]) { + float c[] = new float[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } + + static public String[] concat(String a[], String b[]) { + String c[] = new String[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } + + static public Object concat(Object a, Object b) { + Class type = a.getClass().getComponentType(); + int alength = Array.getLength(a); + int blength = Array.getLength(b); + Object outgoing = Array.newInstance(type, alength + blength); + System.arraycopy(a, 0, outgoing, 0, alength); + System.arraycopy(b, 0, outgoing, alength, blength); + return outgoing; + } + + // + + static public boolean[] reverse(boolean list[]) { + boolean outgoing[] = new boolean[list.length]; + int length1 = list.length - 1; + for (int i = 0; i < list.length; i++) { + outgoing[i] = list[length1 - i]; + } + return outgoing; + } + + static public byte[] reverse(byte list[]) { + byte outgoing[] = new byte[list.length]; + int length1 = list.length - 1; + for (int i = 0; i < list.length; i++) { + outgoing[i] = list[length1 - i]; + } + return outgoing; + } + + static public char[] reverse(char list[]) { + char outgoing[] = new char[list.length]; + int length1 = list.length - 1; + for (int i = 0; i < list.length; i++) { + outgoing[i] = list[length1 - i]; + } + return outgoing; + } + + static public int[] reverse(int list[]) { + int outgoing[] = new int[list.length]; + int length1 = list.length - 1; + for (int i = 0; i < list.length; i++) { + outgoing[i] = list[length1 - i]; + } + return outgoing; + } + + static public float[] reverse(float list[]) { + float outgoing[] = new float[list.length]; + int length1 = list.length - 1; + for (int i = 0; i < list.length; i++) { + outgoing[i] = list[length1 - i]; + } + return outgoing; + } + + static public String[] reverse(String list[]) { + String outgoing[] = new String[list.length]; + int length1 = list.length - 1; + for (int i = 0; i < list.length; i++) { + outgoing[i] = list[length1 - i]; + } + return outgoing; + } + + static public Object reverse(Object list) { + Class type = list.getClass().getComponentType(); + int length = Array.getLength(list); + Object outgoing = Array.newInstance(type, length); + for (int i = 0; i < length; i++) { + Array.set(outgoing, i, Array.get(list, (length - 1) - i)); + } + return outgoing; + } + + + + ////////////////////////////////////////////////////////////// + + // STRINGS + + + /** + * Remove whitespace characters from the beginning and ending + * of a String. Works like String.trim() but includes the + * unicode nbsp character as well. + */ + static public String trim(String str) { + return str.replace('\u00A0', ' ').trim(); + } + + + /** + * Trim the whitespace from a String array. This returns a new + * array and does not affect the passed-in array. + */ + static public String[] trim(String[] array) { + String[] outgoing = new String[array.length]; + for (int i = 0; i < array.length; i++) { + outgoing[i] = array[i].replace('\u00A0', ' ').trim(); + } + return outgoing; + } + + + /** + * Join an array of Strings together as a single String, + * separated by the whatever's passed in for the separator. + */ + static public String join(String str[], char separator) { + return join(str, String.valueOf(separator)); + } + + + /** + * Join an array of Strings together as a single String, + * separated by the whatever's passed in for the separator. + *

+ * To use this on numbers, first pass the array to nf() or nfs() + * to get a list of String objects, then use join on that. + *

+   * e.g. String stuff[] = { "apple", "bear", "cat" };
+   *      String list = join(stuff, ", ");
+   *      // list is now "apple, bear, cat"
+ */ + static public String join(String str[], String separator) { + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < str.length; i++) { + if (i != 0) buffer.append(separator); + buffer.append(str[i]); + } + return buffer.toString(); + } + + + /** + * Split the provided String at wherever whitespace occurs. + * Multiple whitespace (extra spaces or tabs or whatever) + * between items will count as a single break. + *

+ * The whitespace characters are "\t\n\r\f", which are the defaults + * for java.util.StringTokenizer, plus the unicode non-breaking space + * character, which is found commonly on files created by or used + * in conjunction with Mac OS X (character 160, or 0x00A0 in hex). + *

+   * i.e. splitTokens("a b") -> { "a", "b" }
+   *      splitTokens("a    b") -> { "a", "b" }
+   *      splitTokens("a\tb") -> { "a", "b" }
+   *      splitTokens("a \t  b  ") -> { "a", "b" }
+ */ + static public String[] splitTokens(String what) { + return splitTokens(what, WHITESPACE); + } + + + /** + * Splits a string into pieces, using any of the chars in the + * String 'delim' as separator characters. For instance, + * in addition to white space, you might want to treat commas + * as a separator. The delimeter characters won't appear in + * the returned String array. + *
+   * i.e. splitTokens("a, b", " ,") -> { "a", "b" }
+   * 
+ * To include all the whitespace possibilities, use the variable + * WHITESPACE, found in PConstants: + *
+   * i.e. splitTokens("a   | b", WHITESPACE + "|");  ->  { "a", "b" }
+ */ + static public String[] splitTokens(String what, String delim) { + StringTokenizer toker = new StringTokenizer(what, delim); + String pieces[] = new String[toker.countTokens()]; + + int index = 0; + while (toker.hasMoreTokens()) { + pieces[index++] = toker.nextToken(); + } + return pieces; + } + + + /** + * Split a string into pieces along a specific character. + * Most commonly used to break up a String along a space or a tab + * character. + *

+ * This operates differently than the others, where the + * single delimeter is the only breaking point, and consecutive + * delimeters will produce an empty string (""). This way, + * one can split on tab characters, but maintain the column + * alignments (of say an excel file) where there are empty columns. + */ + static public String[] split(String what, char delim) { + // do this so that the exception occurs inside the user's + // program, rather than appearing to be a bug inside split() + if (what == null) return null; + //return split(what, String.valueOf(delim)); // huh + + char chars[] = what.toCharArray(); + int splitCount = 0; //1; + for (int i = 0; i < chars.length; i++) { + if (chars[i] == delim) splitCount++; + } + // make sure that there is something in the input string + //if (chars.length > 0) { + // if the last char is a delimeter, get rid of it.. + //if (chars[chars.length-1] == delim) splitCount--; + // on second thought, i don't agree with this, will disable + //} + if (splitCount == 0) { + String splits[] = new String[1]; + splits[0] = new String(what); + return splits; + } + //int pieceCount = splitCount + 1; + String splits[] = new String[splitCount + 1]; + int splitIndex = 0; + int startIndex = 0; + for (int i = 0; i < chars.length; i++) { + if (chars[i] == delim) { + splits[splitIndex++] = + new String(chars, startIndex, i-startIndex); + startIndex = i + 1; + } + } + //if (startIndex != chars.length) { + splits[splitIndex] = + new String(chars, startIndex, chars.length-startIndex); + //} + return splits; + } + + + /** + * Split a String on a specific delimiter. Unlike Java's String.split() + * method, this does not parse the delimiter as a regexp because it's more + * confusing than necessary, and String.split() is always available for + * those who want regexp. + */ + static public String[] split(String what, String delim) { + ArrayList items = new ArrayList(); + int index; + int offset = 0; + while ((index = what.indexOf(delim, offset)) != -1) { + items.add(what.substring(offset, index)); + offset = index + delim.length(); + } + items.add(what.substring(offset)); + String[] outgoing = new String[items.size()]; + items.toArray(outgoing); + return outgoing; + } + + + /** + * Match a string with a regular expression, and returns the match as an + * array. The first index is the matching expression, and array elements + * [1] and higher represent each of the groups (sequences found in parens). + * + * This uses multiline matching (Pattern.MULTILINE) and dotall mode + * (Pattern.DOTALL) by default, so that ^ and $ match the beginning and + * end of any lines found in the source, and the . operator will also + * pick up newline characters. + */ + static public String[] match(String what, String regexp) { + Pattern p = Pattern.compile(regexp, Pattern.MULTILINE | Pattern.DOTALL); + Matcher m = p.matcher(what); + if (m.find()) { + int count = m.groupCount() + 1; + String[] groups = new String[count]; + for (int i = 0; i < count; i++) { + groups[i] = m.group(i); + } + return groups; + } + return null; + } + + + /** + * Identical to match(), except that it returns an array of all matches in + * the specified String, rather than just the first. + */ + static public String[][] matchAll(String what, String regexp) { + Pattern p = Pattern.compile(regexp, Pattern.MULTILINE | Pattern.DOTALL); + Matcher m = p.matcher(what); + ArrayList results = new ArrayList(); + int count = m.groupCount() + 1; + while (m.find()) { + String[] groups = new String[count]; + for (int i = 0; i < count; i++) { + groups[i] = m.group(i); + } + results.add(groups); + } + if (results.isEmpty()) { + return null; + } + String[][] matches = new String[results.size()][count]; + for (int i = 0; i < matches.length; i++) { + matches[i] = (String[]) results.get(i); + } + return matches; + } + + + + ////////////////////////////////////////////////////////////// + + // CASTING FUNCTIONS, INSERTED BY PREPROC + + + /** + * Convert a char to a boolean. 'T', 't', and '1' will become the + * boolean value true, while 'F', 'f', or '0' will become false. + */ + /* + static final public boolean parseBoolean(char what) { + return ((what == 't') || (what == 'T') || (what == '1')); + } + */ + + /** + *

Convert an integer to a boolean. Because of how Java handles upgrading + * numbers, this will also cover byte and char (as they will upgrade to + * an int without any sort of explicit cast).

+ *

The preprocessor will convert boolean(what) to parseBoolean(what).

+ * @return false if 0, true if any other number + */ + static final public boolean parseBoolean(int what) { + return (what != 0); + } + + /* + // removed because this makes no useful sense + static final public boolean parseBoolean(float what) { + return (what != 0); + } + */ + + /** + * Convert the string "true" or "false" to a boolean. + * @return true if 'what' is "true" or "TRUE", false otherwise + */ + static final public boolean parseBoolean(String what) { + return new Boolean(what).booleanValue(); + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + /* + // removed, no need to introduce strange syntax from other languages + static final public boolean[] parseBoolean(char what[]) { + boolean outgoing[] = new boolean[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = + ((what[i] == 't') || (what[i] == 'T') || (what[i] == '1')); + } + return outgoing; + } + */ + + /** + * Convert a byte array to a boolean array. Each element will be + * evaluated identical to the integer case, where a byte equal + * to zero will return false, and any other value will return true. + * @return array of boolean elements + */ + static final public boolean[] parseBoolean(byte what[]) { + boolean outgoing[] = new boolean[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (what[i] != 0); + } + return outgoing; + } + + /** + * Convert an int array to a boolean array. An int equal + * to zero will return false, and any other value will return true. + * @return array of boolean elements + */ + static final public boolean[] parseBoolean(int what[]) { + boolean outgoing[] = new boolean[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (what[i] != 0); + } + return outgoing; + } + + /* + // removed, not necessary... if necessary, convert to int array first + static final public boolean[] parseBoolean(float what[]) { + boolean outgoing[] = new boolean[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (what[i] != 0); + } + return outgoing; + } + */ + + static final public boolean[] parseBoolean(String what[]) { + boolean outgoing[] = new boolean[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = new Boolean(what[i]).booleanValue(); + } + return outgoing; + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + static final public byte parseByte(boolean what) { + return what ? (byte)1 : 0; + } + + static final public byte parseByte(char what) { + return (byte) what; + } + + static final public byte parseByte(int what) { + return (byte) what; + } + + static final public byte parseByte(float what) { + return (byte) what; + } + + /* + // nixed, no precedent + static final public byte[] parseByte(String what) { // note: array[] + return what.getBytes(); + } + */ + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + static final public byte[] parseByte(boolean what[]) { + byte outgoing[] = new byte[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = what[i] ? (byte)1 : 0; + } + return outgoing; + } + + static final public byte[] parseByte(char what[]) { + byte outgoing[] = new byte[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (byte) what[i]; + } + return outgoing; + } + + static final public byte[] parseByte(int what[]) { + byte outgoing[] = new byte[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (byte) what[i]; + } + return outgoing; + } + + static final public byte[] parseByte(float what[]) { + byte outgoing[] = new byte[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (byte) what[i]; + } + return outgoing; + } + + /* + static final public byte[][] parseByte(String what[]) { // note: array[][] + byte outgoing[][] = new byte[what.length][]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = what[i].getBytes(); + } + return outgoing; + } + */ + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + /* + static final public char parseChar(boolean what) { // 0/1 or T/F ? + return what ? 't' : 'f'; + } + */ + + static final public char parseChar(byte what) { + return (char) (what & 0xff); + } + + static final public char parseChar(int what) { + return (char) what; + } + + /* + static final public char parseChar(float what) { // nonsensical + return (char) what; + } + + static final public char[] parseChar(String what) { // note: array[] + return what.toCharArray(); + } + */ + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + /* + static final public char[] parseChar(boolean what[]) { // 0/1 or T/F ? + char outgoing[] = new char[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = what[i] ? 't' : 'f'; + } + return outgoing; + } + */ + + static final public char[] parseChar(byte what[]) { + char outgoing[] = new char[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (char) (what[i] & 0xff); + } + return outgoing; + } + + static final public char[] parseChar(int what[]) { + char outgoing[] = new char[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (char) what[i]; + } + return outgoing; + } + + /* + static final public char[] parseChar(float what[]) { // nonsensical + char outgoing[] = new char[what.length]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = (char) what[i]; + } + return outgoing; + } + + static final public char[][] parseChar(String what[]) { // note: array[][] + char outgoing[][] = new char[what.length][]; + for (int i = 0; i < what.length; i++) { + outgoing[i] = what[i].toCharArray(); + } + return outgoing; + } + */ + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + static final public int parseInt(boolean what) { + return what ? 1 : 0; + } + + /** + * Note that parseInt() will un-sign a signed byte value. + */ + static final public int parseInt(byte what) { + return what & 0xff; + } + + /** + * Note that parseInt('5') is unlike String in the sense that it + * won't return 5, but the ascii value. This is because ((int) someChar) + * returns the ascii value, and parseInt() is just longhand for the cast. + */ + static final public int parseInt(char what) { + return what; + } + + /** + * Same as floor(), or an (int) cast. + */ + static final public int parseInt(float what) { + return (int) what; + } + + /** + * Parse a String into an int value. Returns 0 if the value is bad. + */ + static final public int parseInt(String what) { + return parseInt(what, 0); + } + + /** + * Parse a String to an int, and provide an alternate value that + * should be used when the number is invalid. + */ + static final public int parseInt(String what, int otherwise) { + try { + int offset = what.indexOf('.'); + if (offset == -1) { + return Integer.parseInt(what); + } else { + return Integer.parseInt(what.substring(0, offset)); + } + } catch (NumberFormatException e) { } + return otherwise; + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + static final public int[] parseInt(boolean what[]) { + int list[] = new int[what.length]; + for (int i = 0; i < what.length; i++) { + list[i] = what[i] ? 1 : 0; + } + return list; + } + + static final public int[] parseInt(byte what[]) { // note this unsigns + int list[] = new int[what.length]; + for (int i = 0; i < what.length; i++) { + list[i] = (what[i] & 0xff); + } + return list; + } + + static final public int[] parseInt(char what[]) { + int list[] = new int[what.length]; + for (int i = 0; i < what.length; i++) { + list[i] = what[i]; + } + return list; + } + + static public int[] parseInt(float what[]) { + int inties[] = new int[what.length]; + for (int i = 0; i < what.length; i++) { + inties[i] = (int)what[i]; + } + return inties; + } + + /** + * Make an array of int elements from an array of String objects. + * If the String can't be parsed as a number, it will be set to zero. + * + * String s[] = { "1", "300", "44" }; + * int numbers[] = parseInt(s); + * + * numbers will contain { 1, 300, 44 } + */ + static public int[] parseInt(String what[]) { + return parseInt(what, 0); + } + + /** + * Make an array of int elements from an array of String objects. + * If the String can't be parsed as a number, its entry in the + * array will be set to the value of the "missing" parameter. + * + * String s[] = { "1", "300", "apple", "44" }; + * int numbers[] = parseInt(s, 9999); + * + * numbers will contain { 1, 300, 9999, 44 } + */ + static public int[] parseInt(String what[], int missing) { + int output[] = new int[what.length]; + for (int i = 0; i < what.length; i++) { + try { + output[i] = Integer.parseInt(what[i]); + } catch (NumberFormatException e) { + output[i] = missing; + } + } + return output; + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + /* + static final public float parseFloat(boolean what) { + return what ? 1 : 0; + } + */ + + /** + * Convert an int to a float value. Also handles bytes because of + * Java's rules for upgrading values. + */ + static final public float parseFloat(int what) { // also handles byte + return (float)what; + } + + static final public float parseFloat(String what) { + return parseFloat(what, Float.NaN); + } + + static final public float parseFloat(String what, float otherwise) { + try { + return new Float(what).floatValue(); + } catch (NumberFormatException e) { } + + return otherwise; + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + /* + static final public float[] parseFloat(boolean what[]) { + float floaties[] = new float[what.length]; + for (int i = 0; i < what.length; i++) { + floaties[i] = what[i] ? 1 : 0; + } + return floaties; + } + + static final public float[] parseFloat(char what[]) { + float floaties[] = new float[what.length]; + for (int i = 0; i < what.length; i++) { + floaties[i] = (char) what[i]; + } + return floaties; + } + */ + + static final public float[] parseByte(byte what[]) { + float floaties[] = new float[what.length]; + for (int i = 0; i < what.length; i++) { + floaties[i] = what[i]; + } + return floaties; + } + + static final public float[] parseFloat(int what[]) { + float floaties[] = new float[what.length]; + for (int i = 0; i < what.length; i++) { + floaties[i] = what[i]; + } + return floaties; + } + + static final public float[] parseFloat(String what[]) { + return parseFloat(what, Float.NaN); + } + + static final public float[] parseFloat(String what[], float missing) { + float output[] = new float[what.length]; + for (int i = 0; i < what.length; i++) { + try { + output[i] = new Float(what[i]).floatValue(); + } catch (NumberFormatException e) { + output[i] = missing; + } + } + return output; + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + static final public String str(boolean x) { + return String.valueOf(x); + } + + static final public String str(byte x) { + return String.valueOf(x); + } + + static final public String str(char x) { + return String.valueOf(x); + } + + static final public String str(int x) { + return String.valueOf(x); + } + + static final public String str(float x) { + return String.valueOf(x); + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + static final public String[] str(boolean x[]) { + String s[] = new String[x.length]; + for (int i = 0; i < x.length; i++) s[i] = String.valueOf(x[i]); + return s; + } + + static final public String[] str(byte x[]) { + String s[] = new String[x.length]; + for (int i = 0; i < x.length; i++) s[i] = String.valueOf(x[i]); + return s; + } + + static final public String[] str(char x[]) { + String s[] = new String[x.length]; + for (int i = 0; i < x.length; i++) s[i] = String.valueOf(x[i]); + return s; + } + + static final public String[] str(int x[]) { + String s[] = new String[x.length]; + for (int i = 0; i < x.length; i++) s[i] = String.valueOf(x[i]); + return s; + } + + static final public String[] str(float x[]) { + String s[] = new String[x.length]; + for (int i = 0; i < x.length; i++) s[i] = String.valueOf(x[i]); + return s; + } + + + ////////////////////////////////////////////////////////////// + + // INT NUMBER FORMATTING + + + /** + * Integer number formatter. + */ + static private NumberFormat int_nf; + static private int int_nf_digits; + static private boolean int_nf_commas; + + + static public String[] nf(int num[], int digits) { + String formatted[] = new String[num.length]; + for (int i = 0; i < formatted.length; i++) { + formatted[i] = nf(num[i], digits); + } + return formatted; + } + + + static public String nf(int num, int digits) { + if ((int_nf != null) && + (int_nf_digits == digits) && + !int_nf_commas) { + return int_nf.format(num); + } + + int_nf = NumberFormat.getInstance(); + int_nf.setGroupingUsed(false); // no commas + int_nf_commas = false; + int_nf.setMinimumIntegerDigits(digits); + int_nf_digits = digits; + return int_nf.format(num); + } + + + static public String[] nfc(int num[]) { + String formatted[] = new String[num.length]; + for (int i = 0; i < formatted.length; i++) { + formatted[i] = nfc(num[i]); + } + return formatted; + } + + + static public String nfc(int num) { + if ((int_nf != null) && + (int_nf_digits == 0) && + int_nf_commas) { + return int_nf.format(num); + } + + int_nf = NumberFormat.getInstance(); + int_nf.setGroupingUsed(true); + int_nf_commas = true; + int_nf.setMinimumIntegerDigits(0); + int_nf_digits = 0; + return int_nf.format(num); + } + + + /** + * number format signed (or space) + * Formats a number but leaves a blank space in the front + * when it's positive so that it can be properly aligned with + * numbers that have a negative sign in front of them. + */ + static public String nfs(int num, int digits) { + return (num < 0) ? nf(num, digits) : (' ' + nf(num, digits)); + } + + static public String[] nfs(int num[], int digits) { + String formatted[] = new String[num.length]; + for (int i = 0; i < formatted.length; i++) { + formatted[i] = nfs(num[i], digits); + } + return formatted; + } + + // + + /** + * number format positive (or plus) + * Formats a number, always placing a - or + sign + * in the front when it's negative or positive. + */ + static public String nfp(int num, int digits) { + return (num < 0) ? nf(num, digits) : ('+' + nf(num, digits)); + } + + static public String[] nfp(int num[], int digits) { + String formatted[] = new String[num.length]; + for (int i = 0; i < formatted.length; i++) { + formatted[i] = nfp(num[i], digits); + } + return formatted; + } + + + + ////////////////////////////////////////////////////////////// + + // FLOAT NUMBER FORMATTING + + + static private NumberFormat float_nf; + static private int float_nf_left, float_nf_right; + static private boolean float_nf_commas; + + + static public String[] nf(float num[], int left, int right) { + String formatted[] = new String[num.length]; + for (int i = 0; i < formatted.length; i++) { + formatted[i] = nf(num[i], left, right); + } + return formatted; + } + + + static public String nf(float num, int left, int right) { + if ((float_nf != null) && + (float_nf_left == left) && + (float_nf_right == right) && + !float_nf_commas) { + return float_nf.format(num); + } + + float_nf = NumberFormat.getInstance(); + float_nf.setGroupingUsed(false); + float_nf_commas = false; + + if (left != 0) float_nf.setMinimumIntegerDigits(left); + if (right != 0) { + float_nf.setMinimumFractionDigits(right); + float_nf.setMaximumFractionDigits(right); + } + float_nf_left = left; + float_nf_right = right; + return float_nf.format(num); + } + + + static public String[] nfc(float num[], int right) { + String formatted[] = new String[num.length]; + for (int i = 0; i < formatted.length; i++) { + formatted[i] = nfc(num[i], right); + } + return formatted; + } + + + static public String nfc(float num, int right) { + if ((float_nf != null) && + (float_nf_left == 0) && + (float_nf_right == right) && + float_nf_commas) { + return float_nf.format(num); + } + + float_nf = NumberFormat.getInstance(); + float_nf.setGroupingUsed(true); + float_nf_commas = true; + + if (right != 0) { + float_nf.setMinimumFractionDigits(right); + float_nf.setMaximumFractionDigits(right); + } + float_nf_left = 0; + float_nf_right = right; + return float_nf.format(num); + } + + + /** + * Number formatter that takes into account whether the number + * has a sign (positive, negative, etc) in front of it. + */ + static public String[] nfs(float num[], int left, int right) { + String formatted[] = new String[num.length]; + for (int i = 0; i < formatted.length; i++) { + formatted[i] = nfs(num[i], left, right); + } + return formatted; + } + + static public String nfs(float num, int left, int right) { + return (num < 0) ? nf(num, left, right) : (' ' + nf(num, left, right)); + } + + + static public String[] nfp(float num[], int left, int right) { + String formatted[] = new String[num.length]; + for (int i = 0; i < formatted.length; i++) { + formatted[i] = nfp(num[i], left, right); + } + return formatted; + } + + static public String nfp(float num, int left, int right) { + return (num < 0) ? nf(num, left, right) : ('+' + nf(num, left, right)); + } + + + + ////////////////////////////////////////////////////////////// + + // HEX/BINARY CONVERSION + + + static final public String hex(byte what) { + return hex(what, 2); + } + + static final public String hex(char what) { + return hex(what, 4); + } + + static final public String hex(int what) { + return hex(what, 8); + } + + static final public String hex(int what, int digits) { + String stuff = Integer.toHexString(what).toUpperCase(); + + int length = stuff.length(); + if (length > digits) { + return stuff.substring(length - digits); + + } else if (length < digits) { + return "00000000".substring(8 - (digits-length)) + stuff; + } + return stuff; + } + + static final public int unhex(String what) { + // has to parse as a Long so that it'll work for numbers bigger than 2^31 + return (int) (Long.parseLong(what, 16)); + } + + // + + /** + * Returns a String that contains the binary value of a byte. + * The returned value will always have 8 digits. + */ + static final public String binary(byte what) { + return binary(what, 8); + } + + /** + * Returns a String that contains the binary value of a char. + * The returned value will always have 16 digits because chars + * are two bytes long. + */ + static final public String binary(char what) { + return binary(what, 16); + } + + /** + * Returns a String that contains the binary value of an int. + * The length depends on the size of the number itself. + * An int can be up to 32 binary digits, but that seems like + * overkill for almost any situation, so this function just + * auto-size. If you want a specific number of digits (like all 32) + * use binary(int what, int digits) to specify how many digits. + */ + static final public String binary(int what) { + return Integer.toBinaryString(what); + //return binary(what, 32); + } + + /** + * Returns a String that contains the binary value of an int. + * The digits parameter determines how many digits will be used. + */ + static final public String binary(int what, int digits) { + String stuff = Integer.toBinaryString(what); + + int length = stuff.length(); + if (length > digits) { + return stuff.substring(length - digits); + + } else if (length < digits) { + int offset = 32 - (digits-length); + return "00000000000000000000000000000000".substring(offset) + stuff; + } + return stuff; + } + + + /** + * Unpack a binary String into an int. + * i.e. unbinary("00001000") would return 8. + */ + static final public int unbinary(String what) { + return Integer.parseInt(what, 2); + } + + + + ////////////////////////////////////////////////////////////// + + // COLOR FUNCTIONS + + // moved here so that they can work without + // the graphics actually being instantiated (outside setup) + + + public final int color(int gray) { + if (g == null) { + if (gray > 255) gray = 255; else if (gray < 0) gray = 0; + return 0xff000000 | (gray << 16) | (gray << 8) | gray; + } + return g.color(gray); + } + + + public final int color(float fgray) { + if (g == null) { + int gray = (int) fgray; + if (gray > 255) gray = 255; else if (gray < 0) gray = 0; + return 0xff000000 | (gray << 16) | (gray << 8) | gray; + } + return g.color(fgray); + } + + + /** + * As of 0116 this also takes color(#FF8800, alpha) + */ + public final int color(int gray, int alpha) { + if (g == null) { + if (alpha > 255) alpha = 255; else if (alpha < 0) alpha = 0; + if (gray > 255) { + // then assume this is actually a #FF8800 + return (alpha << 24) | (gray & 0xFFFFFF); + } else { + //if (gray > 255) gray = 255; else if (gray < 0) gray = 0; + return (alpha << 24) | (gray << 16) | (gray << 8) | gray; + } + } + return g.color(gray, alpha); + } + + + public final int color(float fgray, float falpha) { + if (g == null) { + int gray = (int) fgray; + int alpha = (int) falpha; + if (gray > 255) gray = 255; else if (gray < 0) gray = 0; + if (alpha > 255) alpha = 255; else if (alpha < 0) alpha = 0; + return 0xff000000 | (gray << 16) | (gray << 8) | gray; + } + return g.color(fgray, falpha); + } + + + public final int color(int x, int y, int z) { + if (g == null) { + if (x > 255) x = 255; else if (x < 0) x = 0; + if (y > 255) y = 255; else if (y < 0) y = 0; + if (z > 255) z = 255; else if (z < 0) z = 0; + + return 0xff000000 | (x << 16) | (y << 8) | z; + } + return g.color(x, y, z); + } + + + public final int color(float x, float y, float z) { + if (g == null) { + if (x > 255) x = 255; else if (x < 0) x = 0; + if (y > 255) y = 255; else if (y < 0) y = 0; + if (z > 255) z = 255; else if (z < 0) z = 0; + + return 0xff000000 | ((int)x << 16) | ((int)y << 8) | (int)z; + } + return g.color(x, y, z); + } + + + public final int color(int x, int y, int z, int a) { + if (g == null) { + if (a > 255) a = 255; else if (a < 0) a = 0; + if (x > 255) x = 255; else if (x < 0) x = 0; + if (y > 255) y = 255; else if (y < 0) y = 0; + if (z > 255) z = 255; else if (z < 0) z = 0; + + return (a << 24) | (x << 16) | (y << 8) | z; + } + return g.color(x, y, z, a); + } + + + public final int color(float x, float y, float z, float a) { + if (g == null) { + if (a > 255) a = 255; else if (a < 0) a = 0; + if (x > 255) x = 255; else if (x < 0) x = 0; + if (y > 255) y = 255; else if (y < 0) y = 0; + if (z > 255) z = 255; else if (z < 0) z = 0; + + return ((int)a << 24) | ((int)x << 16) | ((int)y << 8) | (int)z; + } + return g.color(x, y, z, a); + } + + + + ////////////////////////////////////////////////////////////// + + // MAIN + + + /** + * Set this sketch to communicate its state back to the PDE. + *

+ * This uses the stderr stream to write positions of the window + * (so that it will be saved by the PDE for the next run) and + * notify on quit. See more notes in the Worker class. + */ + public void setupExternalMessages() { + + frame.addComponentListener(new ComponentAdapter() { + public void componentMoved(ComponentEvent e) { + Point where = ((Frame) e.getSource()).getLocation(); + System.err.println(PApplet.EXTERNAL_MOVE + " " + + where.x + " " + where.y); + System.err.flush(); // doesn't seem to help or hurt + } + }); + + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { +// System.err.println(PApplet.EXTERNAL_QUIT); +// System.err.flush(); // important +// System.exit(0); + exit(); // don't quit, need to just shut everything down (0133) + } + }); + } + + + /** + * Set up a listener that will fire proper component resize events + * in cases where frame.setResizable(true) is called. + */ + public void setupFrameResizeListener() { + frame.addComponentListener(new ComponentAdapter() { + + public void componentResized(ComponentEvent e) { + // Ignore bad resize events fired during setup to fix + // http://dev.processing.org/bugs/show_bug.cgi?id=341 + // This should also fix the blank screen on Linux bug + // http://dev.processing.org/bugs/show_bug.cgi?id=282 + if (frame.isResizable()) { + // might be multiple resize calls before visible (i.e. first + // when pack() is called, then when it's resized for use). + // ignore them because it's not the user resizing things. + Frame farm = (Frame) e.getComponent(); + if (farm.isVisible()) { + Insets insets = farm.getInsets(); + Dimension windowSize = farm.getSize(); + int usableW = windowSize.width - insets.left - insets.right; + int usableH = windowSize.height - insets.top - insets.bottom; + + // the ComponentListener in PApplet will handle calling size() + setBounds(insets.left, insets.top, usableW, usableH); + } + } + } + }); + } + + + /** + * GIF image of the Processing logo. + */ + static public final byte[] ICON_IMAGE = { + 71, 73, 70, 56, 57, 97, 16, 0, 16, 0, -77, 0, 0, 0, 0, 0, -1, -1, -1, 12, + 12, 13, -15, -15, -14, 45, 57, 74, 54, 80, 111, 47, 71, 97, 62, 88, 117, + 1, 14, 27, 7, 41, 73, 15, 52, 85, 2, 31, 55, 4, 54, 94, 18, 69, 109, 37, + 87, 126, -1, -1, -1, 33, -7, 4, 1, 0, 0, 15, 0, 44, 0, 0, 0, 0, 16, 0, 16, + 0, 0, 4, 122, -16, -107, 114, -86, -67, 83, 30, -42, 26, -17, -100, -45, + 56, -57, -108, 48, 40, 122, -90, 104, 67, -91, -51, 32, -53, 77, -78, -100, + 47, -86, 12, 76, -110, -20, -74, -101, 97, -93, 27, 40, 20, -65, 65, 48, + -111, 99, -20, -112, -117, -123, -47, -105, 24, 114, -112, 74, 69, 84, 25, + 93, 88, -75, 9, 46, 2, 49, 88, -116, -67, 7, -19, -83, 60, 38, 3, -34, 2, + 66, -95, 27, -98, 13, 4, -17, 55, 33, 109, 11, 11, -2, -128, 121, 123, 62, + 91, 120, -128, 127, 122, 115, 102, 2, 119, 0, -116, -113, -119, 6, 102, + 121, -108, -126, 5, 18, 6, 4, -102, -101, -100, 114, 15, 17, 0, 59 + }; + + + /** + * main() method for running this class from the command line. + *

+ * The options shown here are not yet finalized and will be + * changing over the next several releases. + *

+ * The simplest way to turn and applet into an application is to + * add the following code to your program: + *

static public void main(String args[]) {
+   *   PApplet.main(new String[] { "YourSketchName" });
+   * }
+ * This will properly launch your applet from a double-clickable + * .jar or from the command line. + *
+   * Parameters useful for launching or also used by the PDE:
+   *
+   * --location=x,y        upper-lefthand corner of where the applet
+   *                       should appear on screen. if not used,
+   *                       the default is to center on the main screen.
+   *
+   * --present             put the applet into full screen presentation
+   *                       mode. requires java 1.4 or later.
+   *
+   * --exclusive           use full screen exclusive mode when presenting.
+   *                       disables new windows or interaction with other
+   *                       monitors, this is like a "game" mode.
+   *
+   * --hide-stop           use to hide the stop button in situations where
+   *                       you don't want to allow users to exit. also
+   *                       see the FAQ on information for capturing the ESC
+   *                       key when running in presentation mode.
+   *
+   * --stop-color=#xxxxxx  color of the 'stop' text used to quit an
+   *                       sketch when it's in present mode.
+   *
+   * --bgcolor=#xxxxxx     background color of the window.
+   *
+   * --sketch-path         location of where to save files from functions
+   *                       like saveStrings() or saveFrame(). defaults to
+   *                       the folder that the java application was
+   *                       launched from, which means if this isn't set by
+   *                       the pde, everything goes into the same folder
+   *                       as processing.exe.
+   *
+   * --display=n           set what display should be used by this applet.
+   *                       displays are numbered starting from 1.
+   *
+   * Parameters used by Processing when running via the PDE
+   *
+   * --external            set when the applet is being used by the PDE
+   *
+   * --editor-location=x,y position of the upper-lefthand corner of the
+   *                       editor window, for placement of applet window
+   * 
+ */ + static public void main(String args[]) { + // Disable abyssmally slow Sun renderer on OS X 10.5. + if (platform == MACOSX) { + // Only run this on OS X otherwise it can cause a permissions error. + // http://dev.processing.org/bugs/show_bug.cgi?id=976 + System.setProperty("apple.awt.graphics.UseQuartz", "true"); + } + + // This doesn't do anything. +// if (platform == WINDOWS) { +// // For now, disable the D3D renderer on Java 6u10 because +// // it causes problems with Present mode. +// // http://dev.processing.org/bugs/show_bug.cgi?id=1009 +// System.setProperty("sun.java2d.d3d", "false"); +// } + + if (args.length < 1) { + System.err.println("Usage: PApplet "); + System.err.println("For additional options, " + + "see the Javadoc for PApplet"); + System.exit(1); + } + + boolean external = false; + int[] location = null; + int[] editorLocation = null; + + String name = null; + boolean present = false; + boolean exclusive = false; + Color backgroundColor = Color.BLACK; + Color stopColor = Color.GRAY; + GraphicsDevice displayDevice = null; + boolean hideStop = false; + + String param = null, value = null; + + // try to get the user folder. if running under java web start, + // this may cause a security exception if the code is not signed. + // http://processing.org/discourse/yabb_beta/YaBB.cgi?board=Integrate;action=display;num=1159386274 + String folder = null; + try { + folder = System.getProperty("user.dir"); + } catch (Exception e) { } + + int argIndex = 0; + while (argIndex < args.length) { + int equals = args[argIndex].indexOf('='); + if (equals != -1) { + param = args[argIndex].substring(0, equals); + value = args[argIndex].substring(equals + 1); + + if (param.equals(ARGS_EDITOR_LOCATION)) { + external = true; + editorLocation = parseInt(split(value, ',')); + + } else if (param.equals(ARGS_DISPLAY)) { + int deviceIndex = Integer.parseInt(value) - 1; + + //DisplayMode dm = device.getDisplayMode(); + //if ((dm.getWidth() == 1024) && (dm.getHeight() == 768)) { + + GraphicsEnvironment environment = + GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice devices[] = environment.getScreenDevices(); + if ((deviceIndex >= 0) && (deviceIndex < devices.length)) { + displayDevice = devices[deviceIndex]; + } else { + System.err.println("Display " + value + " does not exist, " + + "using the default display instead."); + } + + } else if (param.equals(ARGS_BGCOLOR)) { + if (value.charAt(0) == '#') value = value.substring(1); + backgroundColor = new Color(Integer.parseInt(value, 16)); + + } else if (param.equals(ARGS_STOP_COLOR)) { + if (value.charAt(0) == '#') value = value.substring(1); + stopColor = new Color(Integer.parseInt(value, 16)); + + } else if (param.equals(ARGS_SKETCH_FOLDER)) { + folder = value; + + } else if (param.equals(ARGS_LOCATION)) { + location = parseInt(split(value, ',')); + } + + } else { + if (args[argIndex].equals(ARGS_PRESENT)) { + present = true; + + } else if (args[argIndex].equals(ARGS_EXCLUSIVE)) { + exclusive = true; + + } else if (args[argIndex].equals(ARGS_HIDE_STOP)) { + hideStop = true; + + } else if (args[argIndex].equals(ARGS_EXTERNAL)) { + external = true; + + } else { + name = args[argIndex]; + break; + } + } + argIndex++; + } + + // Set this property before getting into any GUI init code + //System.setProperty("com.apple.mrj.application.apple.menu.about.name", name); + // This )*)(*@#$ Apple crap don't work no matter where you put it + // (static method of the class, at the top of main, wherever) + + if (displayDevice == null) { + GraphicsEnvironment environment = + GraphicsEnvironment.getLocalGraphicsEnvironment(); + displayDevice = environment.getDefaultScreenDevice(); + } + + Frame frame = new Frame(displayDevice.getDefaultConfiguration()); + /* + Frame frame = null; + if (displayDevice != null) { + frame = new Frame(displayDevice.getDefaultConfiguration()); + } else { + frame = new Frame(); + } + */ + //Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + + // remove the grow box by default + // users who want it back can call frame.setResizable(true) + frame.setResizable(false); + + // Set the trimmings around the image + Image image = Toolkit.getDefaultToolkit().createImage(ICON_IMAGE); + frame.setIconImage(image); + frame.setTitle(name); + + final PApplet applet; + try { + Class c = Thread.currentThread().getContextClassLoader().loadClass(name); + applet = (PApplet) c.newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // these are needed before init/start + applet.frame = frame; + applet.sketchPath = folder; + applet.args = PApplet.subset(args, 1); + applet.external = external; + + // Need to save the window bounds at full screen, + // because pack() will cause the bounds to go to zero. + // http://dev.processing.org/bugs/show_bug.cgi?id=923 + Rectangle fullScreenRect = null; + + // For 0149, moving this code (up to the pack() method) before init(). + // For OpenGL (and perhaps other renderers in the future), a peer is + // needed before a GLDrawable can be created. So pack() needs to be + // called on the Frame before applet.init(), which itself calls size(), + // and launches the Thread that will kick off setup(). + // http://dev.processing.org/bugs/show_bug.cgi?id=891 + // http://dev.processing.org/bugs/show_bug.cgi?id=908 + if (present) { + frame.setUndecorated(true); + frame.setBackground(backgroundColor); + if (exclusive) { + displayDevice.setFullScreenWindow(frame); + fullScreenRect = frame.getBounds(); + } else { + DisplayMode mode = displayDevice.getDisplayMode(); + fullScreenRect = new Rectangle(0, 0, mode.getWidth(), mode.getHeight()); + frame.setBounds(fullScreenRect); + frame.setVisible(true); + } + } + frame.setLayout(null); + frame.add(applet); + if (present) { + frame.invalidate(); + } else { + frame.pack(); + } + // insufficient, places the 100x100 sketches offset strangely + //frame.validate(); + + applet.init(); + + // Wait until the applet has figured out its width. + // In a static mode app, this will be after setup() has completed, + // and the empty draw() has set "finished" to true. + // TODO make sure this won't hang if the applet has an exception. + while (applet.defaultSize && !applet.finished) { + //System.out.println("default size"); + try { + Thread.sleep(5); + + } catch (InterruptedException e) { + //System.out.println("interrupt"); + } + } + //println("not default size " + applet.width + " " + applet.height); + //println(" (g width/height is " + applet.g.width + "x" + applet.g.height + ")"); + + if (present) { + // After the pack(), the screen bounds are gonna be 0s + frame.setBounds(fullScreenRect); + applet.setBounds((fullScreenRect.width - applet.width) / 2, + (fullScreenRect.height - applet.height) / 2, + applet.width, applet.height); + + if (!hideStop) { + Label label = new Label("stop"); + label.setForeground(stopColor); + label.addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + System.exit(0); + } + }); + frame.add(label); + + Dimension labelSize = label.getPreferredSize(); + // sometimes shows up truncated on mac + //System.out.println("label width is " + labelSize.width); + labelSize = new Dimension(100, labelSize.height); + label.setSize(labelSize); + label.setLocation(20, fullScreenRect.height - labelSize.height - 20); + } + + // not always running externally when in present mode + if (external) { + applet.setupExternalMessages(); + } + + } else { // if not presenting + // can't do pack earlier cuz present mode don't like it + // (can't go full screen with a frame after calling pack) + // frame.pack(); // get insets. get more. + Insets insets = frame.getInsets(); + + int windowW = Math.max(applet.width, MIN_WINDOW_WIDTH) + + insets.left + insets.right; + int windowH = Math.max(applet.height, MIN_WINDOW_HEIGHT) + + insets.top + insets.bottom; + + frame.setSize(windowW, windowH); + + if (location != null) { + // a specific location was received from PdeRuntime + // (applet has been run more than once, user placed window) + frame.setLocation(location[0], location[1]); + + } else if (external) { + int locationX = editorLocation[0] - 20; + int locationY = editorLocation[1]; + + if (locationX - windowW > 10) { + // if it fits to the left of the window + frame.setLocation(locationX - windowW, locationY); + + } else { // doesn't fit + // if it fits inside the editor window, + // offset slightly from upper lefthand corner + // so that it's plunked inside the text area + locationX = editorLocation[0] + 66; + locationY = editorLocation[1] + 66; + + if ((locationX + windowW > applet.screen.width - 33) || + (locationY + windowH > applet.screen.height - 33)) { + // otherwise center on screen + locationX = (applet.screen.width - windowW) / 2; + locationY = (applet.screen.height - windowH) / 2; + } + frame.setLocation(locationX, locationY); + } + } else { // just center on screen + frame.setLocation((applet.screen.width - applet.width) / 2, + (applet.screen.height - applet.height) / 2); + } + + if (backgroundColor == Color.black) { //BLACK) { + // this means no bg color unless specified + backgroundColor = SystemColor.control; + } + frame.setBackground(backgroundColor); + + int usableWindowH = windowH - insets.top - insets.bottom; + applet.setBounds((windowW - applet.width)/2, + insets.top + (usableWindowH - applet.height)/2, + applet.width, applet.height); + + if (external) { + applet.setupExternalMessages(); + + } else { // !external + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + System.exit(0); + } + }); + } + + // handle frame resizing events + applet.setupFrameResizeListener(); + + // all set for rockin + if (applet.displayable()) { + frame.setVisible(true); + } + } + + applet.requestFocus(); // ask for keydowns + //System.out.println("exiting main()"); + } + + + ////////////////////////////////////////////////////////////// + + + /** + * Begin recording to a new renderer of the specified type, using the width + * and height of the main drawing surface. + */ + public PGraphics beginRecord(String renderer, String filename) { + filename = insertFrame(filename); + PGraphics rec = createGraphics(width, height, renderer, filename); + beginRecord(rec); + return rec; + } + + + /** + * Begin recording (echoing) commands to the specified PGraphics object. + */ + public void beginRecord(PGraphics recorder) { + this.recorder = recorder; + recorder.beginDraw(); + } + + + public void endRecord() { + if (recorder != null) { + recorder.endDraw(); + recorder.dispose(); + recorder = null; + } + } + + + /** + * Begin recording raw shape data to a renderer of the specified type, + * using the width and height of the main drawing surface. + * + * If hashmarks (###) are found in the filename, they'll be replaced + * by the current frame number (frameCount). + */ + public PGraphics beginRaw(String renderer, String filename) { + filename = insertFrame(filename); + PGraphics rec = createGraphics(width, height, renderer, filename); + g.beginRaw(rec); + return rec; + } + + + /** + * Begin recording raw shape data to the specified renderer. + * + * This simply echoes to g.beginRaw(), but since is placed here (rather than + * generated by preproc.pl) for clarity and so that it doesn't echo the + * command should beginRecord() be in use. + */ + public void beginRaw(PGraphics rawGraphics) { + g.beginRaw(rawGraphics); + } + + + /** + * Stop recording raw shape data to the specified renderer. + * + * This simply echoes to g.beginRaw(), but since is placed here (rather than + * generated by preproc.pl) for clarity and so that it doesn't echo the + * command should beginRecord() be in use. + */ + public void endRaw() { + g.endRaw(); + } + + + ////////////////////////////////////////////////////////////// + + + /** + * Override the g.pixels[] function to set the pixels[] array + * that's part of the PApplet object. Allows the use of + * pixels[] in the code, rather than g.pixels[]. + */ + public void loadPixels() { + g.loadPixels(); + pixels = g.pixels; + } + + + public void updatePixels() { + g.updatePixels(); + } + + + public void updatePixels(int x1, int y1, int x2, int y2) { + g.updatePixels(x1, y1, x2, y2); + } + + + ////////////////////////////////////////////////////////////// + + // everything below this line is automatically generated. no touch. + // public functions for processing.core + + + public void flush() { + if (recorder != null) recorder.flush(); + g.flush(); + } + + + public void hint(int which) { + if (recorder != null) recorder.hint(which); + g.hint(which); + } + + + public void beginShape() { + if (recorder != null) recorder.beginShape(); + g.beginShape(); + } + + + public void beginShape(int kind) { + if (recorder != null) recorder.beginShape(kind); + g.beginShape(kind); + } + + + public void edge(boolean edge) { + if (recorder != null) recorder.edge(edge); + g.edge(edge); + } + + + public void normal(float nx, float ny, float nz) { + if (recorder != null) recorder.normal(nx, ny, nz); + g.normal(nx, ny, nz); + } + + + public void textureMode(int mode) { + if (recorder != null) recorder.textureMode(mode); + g.textureMode(mode); + } + + + public void texture(PImage image) { + if (recorder != null) recorder.texture(image); + g.texture(image); + } + + + public void vertex(float x, float y) { + if (recorder != null) recorder.vertex(x, y); + g.vertex(x, y); + } + + + public void vertex(float x, float y, float z) { + if (recorder != null) recorder.vertex(x, y, z); + g.vertex(x, y, z); + } + + + public void vertex(float[] v) { + if (recorder != null) recorder.vertex(v); + g.vertex(v); + } + + + public void vertex(float x, float y, float u, float v) { + if (recorder != null) recorder.vertex(x, y, u, v); + g.vertex(x, y, u, v); + } + + + public void vertex(float x, float y, float z, float u, float v) { + if (recorder != null) recorder.vertex(x, y, z, u, v); + g.vertex(x, y, z, u, v); + } + + + public void breakShape() { + if (recorder != null) recorder.breakShape(); + g.breakShape(); + } + + + public void endShape() { + if (recorder != null) recorder.endShape(); + g.endShape(); + } + + + public void endShape(int mode) { + if (recorder != null) recorder.endShape(mode); + g.endShape(mode); + } + + + public void bezierVertex(float x2, float y2, + float x3, float y3, + float x4, float y4) { + if (recorder != null) recorder.bezierVertex(x2, y2, x3, y3, x4, y4); + g.bezierVertex(x2, y2, x3, y3, x4, y4); + } + + + public void bezierVertex(float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + if (recorder != null) recorder.bezierVertex(x2, y2, z2, x3, y3, z3, x4, y4, z4); + g.bezierVertex(x2, y2, z2, x3, y3, z3, x4, y4, z4); + } + + + public void curveVertex(float x, float y) { + if (recorder != null) recorder.curveVertex(x, y); + g.curveVertex(x, y); + } + + + public void curveVertex(float x, float y, float z) { + if (recorder != null) recorder.curveVertex(x, y, z); + g.curveVertex(x, y, z); + } + + + public void point(float x, float y) { + if (recorder != null) recorder.point(x, y); + g.point(x, y); + } + + + public void point(float x, float y, float z) { + if (recorder != null) recorder.point(x, y, z); + g.point(x, y, z); + } + + + public void line(float x1, float y1, float x2, float y2) { + if (recorder != null) recorder.line(x1, y1, x2, y2); + g.line(x1, y1, x2, y2); + } + + + public void line(float x1, float y1, float z1, + float x2, float y2, float z2) { + if (recorder != null) recorder.line(x1, y1, z1, x2, y2, z2); + g.line(x1, y1, z1, x2, y2, z2); + } + + + public void triangle(float x1, float y1, float x2, float y2, + float x3, float y3) { + if (recorder != null) recorder.triangle(x1, y1, x2, y2, x3, y3); + g.triangle(x1, y1, x2, y2, x3, y3); + } + + + public void quad(float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4) { + if (recorder != null) recorder.quad(x1, y1, x2, y2, x3, y3, x4, y4); + g.quad(x1, y1, x2, y2, x3, y3, x4, y4); + } + + + public void rectMode(int mode) { + if (recorder != null) recorder.rectMode(mode); + g.rectMode(mode); + } + + + public void rect(float a, float b, float c, float d) { + if (recorder != null) recorder.rect(a, b, c, d); + g.rect(a, b, c, d); + } + + + public void ellipseMode(int mode) { + if (recorder != null) recorder.ellipseMode(mode); + g.ellipseMode(mode); + } + + + public void ellipse(float a, float b, float c, float d) { + if (recorder != null) recorder.ellipse(a, b, c, d); + g.ellipse(a, b, c, d); + } + + + public void arc(float a, float b, float c, float d, + float start, float stop) { + if (recorder != null) recorder.arc(a, b, c, d, start, stop); + g.arc(a, b, c, d, start, stop); + } + + + public void box(float size) { + if (recorder != null) recorder.box(size); + g.box(size); + } + + + public void box(float w, float h, float d) { + if (recorder != null) recorder.box(w, h, d); + g.box(w, h, d); + } + + + public void sphereDetail(int res) { + if (recorder != null) recorder.sphereDetail(res); + g.sphereDetail(res); + } + + + public void sphereDetail(int ures, int vres) { + if (recorder != null) recorder.sphereDetail(ures, vres); + g.sphereDetail(ures, vres); + } + + + public void sphere(float r) { + if (recorder != null) recorder.sphere(r); + g.sphere(r); + } + + + public float bezierPoint(float a, float b, float c, float d, float t) { + return g.bezierPoint(a, b, c, d, t); + } + + + public float bezierTangent(float a, float b, float c, float d, float t) { + return g.bezierTangent(a, b, c, d, t); + } + + + public void bezierDetail(int detail) { + if (recorder != null) recorder.bezierDetail(detail); + g.bezierDetail(detail); + } + + + public void bezier(float x1, float y1, + float x2, float y2, + float x3, float y3, + float x4, float y4) { + if (recorder != null) recorder.bezier(x1, y1, x2, y2, x3, y3, x4, y4); + g.bezier(x1, y1, x2, y2, x3, y3, x4, y4); + } + + + public void bezier(float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + if (recorder != null) recorder.bezier(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4); + g.bezier(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4); + } + + + public float curvePoint(float a, float b, float c, float d, float t) { + return g.curvePoint(a, b, c, d, t); + } + + + public float curveTangent(float a, float b, float c, float d, float t) { + return g.curveTangent(a, b, c, d, t); + } + + + public void curveDetail(int detail) { + if (recorder != null) recorder.curveDetail(detail); + g.curveDetail(detail); + } + + + public void curveTightness(float tightness) { + if (recorder != null) recorder.curveTightness(tightness); + g.curveTightness(tightness); + } + + + public void curve(float x1, float y1, + float x2, float y2, + float x3, float y3, + float x4, float y4) { + if (recorder != null) recorder.curve(x1, y1, x2, y2, x3, y3, x4, y4); + g.curve(x1, y1, x2, y2, x3, y3, x4, y4); + } + + + public void curve(float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + if (recorder != null) recorder.curve(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4); + g.curve(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4); + } + + + public void smooth() { + if (recorder != null) recorder.smooth(); + g.smooth(); + } + + + public void noSmooth() { + if (recorder != null) recorder.noSmooth(); + g.noSmooth(); + } + + + public void imageMode(int mode) { + if (recorder != null) recorder.imageMode(mode); + g.imageMode(mode); + } + + + public void image(PImage image, float x, float y) { + if (recorder != null) recorder.image(image, x, y); + g.image(image, x, y); + } + + + public void image(PImage image, float x, float y, float c, float d) { + if (recorder != null) recorder.image(image, x, y, c, d); + g.image(image, x, y, c, d); + } + + + public void image(PImage image, + float a, float b, float c, float d, + int u1, int v1, int u2, int v2) { + if (recorder != null) recorder.image(image, a, b, c, d, u1, v1, u2, v2); + g.image(image, a, b, c, d, u1, v1, u2, v2); + } + + + public void shapeMode(int mode) { + if (recorder != null) recorder.shapeMode(mode); + g.shapeMode(mode); + } + + + public void shape(PShape shape) { + if (recorder != null) recorder.shape(shape); + g.shape(shape); + } + + + public void shape(PShape shape, float x, float y) { + if (recorder != null) recorder.shape(shape, x, y); + g.shape(shape, x, y); + } + + + public void shape(PShape shape, float x, float y, float c, float d) { + if (recorder != null) recorder.shape(shape, x, y, c, d); + g.shape(shape, x, y, c, d); + } + + + public void textAlign(int align) { + if (recorder != null) recorder.textAlign(align); + g.textAlign(align); + } + + + public void textAlign(int alignX, int alignY) { + if (recorder != null) recorder.textAlign(alignX, alignY); + g.textAlign(alignX, alignY); + } + + + public float textAscent() { + return g.textAscent(); + } + + + public float textDescent() { + return g.textDescent(); + } + + + public void textFont(PFont which) { + if (recorder != null) recorder.textFont(which); + g.textFont(which); + } + + + public void textFont(PFont which, float size) { + if (recorder != null) recorder.textFont(which, size); + g.textFont(which, size); + } + + + public void textLeading(float leading) { + if (recorder != null) recorder.textLeading(leading); + g.textLeading(leading); + } + + + public void textMode(int mode) { + if (recorder != null) recorder.textMode(mode); + g.textMode(mode); + } + + + public void textSize(float size) { + if (recorder != null) recorder.textSize(size); + g.textSize(size); + } + + + public float textWidth(char c) { + return g.textWidth(c); + } + + + public float textWidth(String str) { + return g.textWidth(str); + } + + + public void text(char c) { + if (recorder != null) recorder.text(c); + g.text(c); + } + + + public void text(char c, float x, float y) { + if (recorder != null) recorder.text(c, x, y); + g.text(c, x, y); + } + + + public void text(char c, float x, float y, float z) { + if (recorder != null) recorder.text(c, x, y, z); + g.text(c, x, y, z); + } + + + public void text(String str) { + if (recorder != null) recorder.text(str); + g.text(str); + } + + + public void text(String str, float x, float y) { + if (recorder != null) recorder.text(str, x, y); + g.text(str, x, y); + } + + + public void text(char[] chars, int start, int stop, float x, float y) { + if (recorder != null) recorder.text(chars, start, stop, x, y); + g.text(chars, start, stop, x, y); + } + + + public void text(String str, float x, float y, float z) { + if (recorder != null) recorder.text(str, x, y, z); + g.text(str, x, y, z); + } + + + public void text(char[] chars, int start, int stop, + float x, float y, float z) { + if (recorder != null) recorder.text(chars, start, stop, x, y, z); + g.text(chars, start, stop, x, y, z); + } + + + public void text(String str, float x1, float y1, float x2, float y2) { + if (recorder != null) recorder.text(str, x1, y1, x2, y2); + g.text(str, x1, y1, x2, y2); + } + + + public void text(String s, float x1, float y1, float x2, float y2, float z) { + if (recorder != null) recorder.text(s, x1, y1, x2, y2, z); + g.text(s, x1, y1, x2, y2, z); + } + + + public void text(int num, float x, float y) { + if (recorder != null) recorder.text(num, x, y); + g.text(num, x, y); + } + + + public void text(int num, float x, float y, float z) { + if (recorder != null) recorder.text(num, x, y, z); + g.text(num, x, y, z); + } + + + public void text(float num, float x, float y) { + if (recorder != null) recorder.text(num, x, y); + g.text(num, x, y); + } + + + public void text(float num, float x, float y, float z) { + if (recorder != null) recorder.text(num, x, y, z); + g.text(num, x, y, z); + } + + + public void pushMatrix() { + if (recorder != null) recorder.pushMatrix(); + g.pushMatrix(); + } + + + public void popMatrix() { + if (recorder != null) recorder.popMatrix(); + g.popMatrix(); + } + + + public void translate(float tx, float ty) { + if (recorder != null) recorder.translate(tx, ty); + g.translate(tx, ty); + } + + + public void translate(float tx, float ty, float tz) { + if (recorder != null) recorder.translate(tx, ty, tz); + g.translate(tx, ty, tz); + } + + + public void rotate(float angle) { + if (recorder != null) recorder.rotate(angle); + g.rotate(angle); + } + + + public void rotateX(float angle) { + if (recorder != null) recorder.rotateX(angle); + g.rotateX(angle); + } + + + public void rotateY(float angle) { + if (recorder != null) recorder.rotateY(angle); + g.rotateY(angle); + } + + + public void rotateZ(float angle) { + if (recorder != null) recorder.rotateZ(angle); + g.rotateZ(angle); + } + + + public void rotate(float angle, float vx, float vy, float vz) { + if (recorder != null) recorder.rotate(angle, vx, vy, vz); + g.rotate(angle, vx, vy, vz); + } + + + public void scale(float s) { + if (recorder != null) recorder.scale(s); + g.scale(s); + } + + + public void scale(float sx, float sy) { + if (recorder != null) recorder.scale(sx, sy); + g.scale(sx, sy); + } + + + public void scale(float x, float y, float z) { + if (recorder != null) recorder.scale(x, y, z); + g.scale(x, y, z); + } + + + public void resetMatrix() { + if (recorder != null) recorder.resetMatrix(); + g.resetMatrix(); + } + + + public void applyMatrix(PMatrix source) { + if (recorder != null) recorder.applyMatrix(source); + g.applyMatrix(source); + } + + + public void applyMatrix(PMatrix2D source) { + if (recorder != null) recorder.applyMatrix(source); + g.applyMatrix(source); + } + + + public void applyMatrix(float n00, float n01, float n02, + float n10, float n11, float n12) { + if (recorder != null) recorder.applyMatrix(n00, n01, n02, n10, n11, n12); + g.applyMatrix(n00, n01, n02, n10, n11, n12); + } + + + public void applyMatrix(PMatrix3D source) { + if (recorder != null) recorder.applyMatrix(source); + g.applyMatrix(source); + } + + + public void applyMatrix(float n00, float n01, float n02, float n03, + float n10, float n11, float n12, float n13, + float n20, float n21, float n22, float n23, + float n30, float n31, float n32, float n33) { + if (recorder != null) recorder.applyMatrix(n00, n01, n02, n03, n10, n11, n12, n13, n20, n21, n22, n23, n30, n31, n32, n33); + g.applyMatrix(n00, n01, n02, n03, n10, n11, n12, n13, n20, n21, n22, n23, n30, n31, n32, n33); + } + + + public PMatrix getMatrix() { + return g.getMatrix(); + } + + + public PMatrix2D getMatrix(PMatrix2D target) { + return g.getMatrix(target); + } + + + public PMatrix3D getMatrix(PMatrix3D target) { + return g.getMatrix(target); + } + + + public void setMatrix(PMatrix source) { + if (recorder != null) recorder.setMatrix(source); + g.setMatrix(source); + } + + + public void setMatrix(PMatrix2D source) { + if (recorder != null) recorder.setMatrix(source); + g.setMatrix(source); + } + + + public void setMatrix(PMatrix3D source) { + if (recorder != null) recorder.setMatrix(source); + g.setMatrix(source); + } + + + public void printMatrix() { + if (recorder != null) recorder.printMatrix(); + g.printMatrix(); + } + + + public void beginCamera() { + if (recorder != null) recorder.beginCamera(); + g.beginCamera(); + } + + + public void endCamera() { + if (recorder != null) recorder.endCamera(); + g.endCamera(); + } + + + public void camera() { + if (recorder != null) recorder.camera(); + g.camera(); + } + + + public void camera(float eyeX, float eyeY, float eyeZ, + float centerX, float centerY, float centerZ, + float upX, float upY, float upZ) { + if (recorder != null) recorder.camera(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ); + g.camera(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ); + } + + + public void printCamera() { + if (recorder != null) recorder.printCamera(); + g.printCamera(); + } + + + public void ortho() { + if (recorder != null) recorder.ortho(); + g.ortho(); + } + + + public void ortho(float left, float right, + float bottom, float top, + float near, float far) { + if (recorder != null) recorder.ortho(left, right, bottom, top, near, far); + g.ortho(left, right, bottom, top, near, far); + } + + + public void perspective() { + if (recorder != null) recorder.perspective(); + g.perspective(); + } + + + public void perspective(float fovy, float aspect, float zNear, float zFar) { + if (recorder != null) recorder.perspective(fovy, aspect, zNear, zFar); + g.perspective(fovy, aspect, zNear, zFar); + } + + + public void frustum(float left, float right, + float bottom, float top, + float near, float far) { + if (recorder != null) recorder.frustum(left, right, bottom, top, near, far); + g.frustum(left, right, bottom, top, near, far); + } + + + public void printProjection() { + if (recorder != null) recorder.printProjection(); + g.printProjection(); + } + + + public float screenX(float x, float y) { + return g.screenX(x, y); + } + + + public float screenY(float x, float y) { + return g.screenY(x, y); + } + + + public float screenX(float x, float y, float z) { + return g.screenX(x, y, z); + } + + + public float screenY(float x, float y, float z) { + return g.screenY(x, y, z); + } + + + public float screenZ(float x, float y, float z) { + return g.screenZ(x, y, z); + } + + + public float modelX(float x, float y, float z) { + return g.modelX(x, y, z); + } + + + public float modelY(float x, float y, float z) { + return g.modelY(x, y, z); + } + + + public float modelZ(float x, float y, float z) { + return g.modelZ(x, y, z); + } + + + public void pushStyle() { + if (recorder != null) recorder.pushStyle(); + g.pushStyle(); + } + + + public void popStyle() { + if (recorder != null) recorder.popStyle(); + g.popStyle(); + } + + + public void style(PStyle s) { + if (recorder != null) recorder.style(s); + g.style(s); + } + + + public void strokeWeight(float weight) { + if (recorder != null) recorder.strokeWeight(weight); + g.strokeWeight(weight); + } + + + public void strokeJoin(int join) { + if (recorder != null) recorder.strokeJoin(join); + g.strokeJoin(join); + } + + + public void strokeCap(int cap) { + if (recorder != null) recorder.strokeCap(cap); + g.strokeCap(cap); + } + + + public void noStroke() { + if (recorder != null) recorder.noStroke(); + g.noStroke(); + } + + + public void stroke(int rgb) { + if (recorder != null) recorder.stroke(rgb); + g.stroke(rgb); + } + + + public void stroke(int rgb, float alpha) { + if (recorder != null) recorder.stroke(rgb, alpha); + g.stroke(rgb, alpha); + } + + + public void stroke(float gray) { + if (recorder != null) recorder.stroke(gray); + g.stroke(gray); + } + + + public void stroke(float gray, float alpha) { + if (recorder != null) recorder.stroke(gray, alpha); + g.stroke(gray, alpha); + } + + + public void stroke(float x, float y, float z) { + if (recorder != null) recorder.stroke(x, y, z); + g.stroke(x, y, z); + } + + + public void stroke(float x, float y, float z, float a) { + if (recorder != null) recorder.stroke(x, y, z, a); + g.stroke(x, y, z, a); + } + + + public void noTint() { + if (recorder != null) recorder.noTint(); + g.noTint(); + } + + + public void tint(int rgb) { + if (recorder != null) recorder.tint(rgb); + g.tint(rgb); + } + + + public void tint(int rgb, float alpha) { + if (recorder != null) recorder.tint(rgb, alpha); + g.tint(rgb, alpha); + } + + + public void tint(float gray) { + if (recorder != null) recorder.tint(gray); + g.tint(gray); + } + + + public void tint(float gray, float alpha) { + if (recorder != null) recorder.tint(gray, alpha); + g.tint(gray, alpha); + } + + + public void tint(float x, float y, float z) { + if (recorder != null) recorder.tint(x, y, z); + g.tint(x, y, z); + } + + + public void tint(float x, float y, float z, float a) { + if (recorder != null) recorder.tint(x, y, z, a); + g.tint(x, y, z, a); + } + + + public void noFill() { + if (recorder != null) recorder.noFill(); + g.noFill(); + } + + + public void fill(int rgb) { + if (recorder != null) recorder.fill(rgb); + g.fill(rgb); + } + + + public void fill(int rgb, float alpha) { + if (recorder != null) recorder.fill(rgb, alpha); + g.fill(rgb, alpha); + } + + + public void fill(float gray) { + if (recorder != null) recorder.fill(gray); + g.fill(gray); + } + + + public void fill(float gray, float alpha) { + if (recorder != null) recorder.fill(gray, alpha); + g.fill(gray, alpha); + } + + + public void fill(float x, float y, float z) { + if (recorder != null) recorder.fill(x, y, z); + g.fill(x, y, z); + } + + + public void fill(float x, float y, float z, float a) { + if (recorder != null) recorder.fill(x, y, z, a); + g.fill(x, y, z, a); + } + + + public void ambient(int rgb) { + if (recorder != null) recorder.ambient(rgb); + g.ambient(rgb); + } + + + public void ambient(float gray) { + if (recorder != null) recorder.ambient(gray); + g.ambient(gray); + } + + + public void ambient(float x, float y, float z) { + if (recorder != null) recorder.ambient(x, y, z); + g.ambient(x, y, z); + } + + + public void specular(int rgb) { + if (recorder != null) recorder.specular(rgb); + g.specular(rgb); + } + + + public void specular(float gray) { + if (recorder != null) recorder.specular(gray); + g.specular(gray); + } + + + public void specular(float x, float y, float z) { + if (recorder != null) recorder.specular(x, y, z); + g.specular(x, y, z); + } + + + public void shininess(float shine) { + if (recorder != null) recorder.shininess(shine); + g.shininess(shine); + } + + + public void emissive(int rgb) { + if (recorder != null) recorder.emissive(rgb); + g.emissive(rgb); + } + + + public void emissive(float gray) { + if (recorder != null) recorder.emissive(gray); + g.emissive(gray); + } + + + public void emissive(float x, float y, float z) { + if (recorder != null) recorder.emissive(x, y, z); + g.emissive(x, y, z); + } + + + public void lights() { + if (recorder != null) recorder.lights(); + g.lights(); + } + + + public void noLights() { + if (recorder != null) recorder.noLights(); + g.noLights(); + } + + + public void ambientLight(float red, float green, float blue) { + if (recorder != null) recorder.ambientLight(red, green, blue); + g.ambientLight(red, green, blue); + } + + + public void ambientLight(float red, float green, float blue, + float x, float y, float z) { + if (recorder != null) recorder.ambientLight(red, green, blue, x, y, z); + g.ambientLight(red, green, blue, x, y, z); + } + + + public void directionalLight(float red, float green, float blue, + float nx, float ny, float nz) { + if (recorder != null) recorder.directionalLight(red, green, blue, nx, ny, nz); + g.directionalLight(red, green, blue, nx, ny, nz); + } + + + public void pointLight(float red, float green, float blue, + float x, float y, float z) { + if (recorder != null) recorder.pointLight(red, green, blue, x, y, z); + g.pointLight(red, green, blue, x, y, z); + } + + + public void spotLight(float red, float green, float blue, + float x, float y, float z, + float nx, float ny, float nz, + float angle, float concentration) { + if (recorder != null) recorder.spotLight(red, green, blue, x, y, z, nx, ny, nz, angle, concentration); + g.spotLight(red, green, blue, x, y, z, nx, ny, nz, angle, concentration); + } + + + public void lightFalloff(float constant, float linear, float quadratic) { + if (recorder != null) recorder.lightFalloff(constant, linear, quadratic); + g.lightFalloff(constant, linear, quadratic); + } + + + public void lightSpecular(float x, float y, float z) { + if (recorder != null) recorder.lightSpecular(x, y, z); + g.lightSpecular(x, y, z); + } + + + public void background(int rgb) { + if (recorder != null) recorder.background(rgb); + g.background(rgb); + } + + + public void background(int rgb, float alpha) { + if (recorder != null) recorder.background(rgb, alpha); + g.background(rgb, alpha); + } + + + public void background(float gray) { + if (recorder != null) recorder.background(gray); + g.background(gray); + } + + + public void background(float gray, float alpha) { + if (recorder != null) recorder.background(gray, alpha); + g.background(gray, alpha); + } + + + public void background(float x, float y, float z) { + if (recorder != null) recorder.background(x, y, z); + g.background(x, y, z); + } + + + public void background(float x, float y, float z, float a) { + if (recorder != null) recorder.background(x, y, z, a); + g.background(x, y, z, a); + } + + + public void background(PImage image) { + if (recorder != null) recorder.background(image); + g.background(image); + } + + + public void colorMode(int mode) { + if (recorder != null) recorder.colorMode(mode); + g.colorMode(mode); + } + + + public void colorMode(int mode, float max) { + if (recorder != null) recorder.colorMode(mode, max); + g.colorMode(mode, max); + } + + + public void colorMode(int mode, float maxX, float maxY, float maxZ) { + if (recorder != null) recorder.colorMode(mode, maxX, maxY, maxZ); + g.colorMode(mode, maxX, maxY, maxZ); + } + + + public void colorMode(int mode, + float maxX, float maxY, float maxZ, float maxA) { + if (recorder != null) recorder.colorMode(mode, maxX, maxY, maxZ, maxA); + g.colorMode(mode, maxX, maxY, maxZ, maxA); + } + + + public final float alpha(int what) { + return g.alpha(what); + } + + + public final float red(int what) { + return g.red(what); + } + + + public final float green(int what) { + return g.green(what); + } + + + public final float blue(int what) { + return g.blue(what); + } + + + public final float hue(int what) { + return g.hue(what); + } + + + public final float saturation(int what) { + return g.saturation(what); + } + + + public final float brightness(int what) { + return g.brightness(what); + } + + + public int lerpColor(int c1, int c2, float amt) { + return g.lerpColor(c1, c2, amt); + } + + + static public int lerpColor(int c1, int c2, float amt, int mode) { + return PGraphics.lerpColor(c1, c2, amt, mode); + } + + + public boolean displayable() { + return g.displayable(); + } + + + public void setCache(Object parent, Object storage) { + if (recorder != null) recorder.setCache(parent, storage); + g.setCache(parent, storage); + } + + + public Object getCache(Object parent) { + return g.getCache(parent); + } + + + public void removeCache(Object parent) { + if (recorder != null) recorder.removeCache(parent); + g.removeCache(parent); + } + + + public int get(int x, int y) { + return g.get(x, y); + } + + + public PImage get(int x, int y, int w, int h) { + return g.get(x, y, w, h); + } + + + public PImage get() { + return g.get(); + } + + + public void set(int x, int y, int c) { + if (recorder != null) recorder.set(x, y, c); + g.set(x, y, c); + } + + + public void set(int x, int y, PImage src) { + if (recorder != null) recorder.set(x, y, src); + g.set(x, y, src); + } + + + public void mask(int alpha[]) { + if (recorder != null) recorder.mask(alpha); + g.mask(alpha); + } + + + public void mask(PImage alpha) { + if (recorder != null) recorder.mask(alpha); + g.mask(alpha); + } + + + public void filter(int kind) { + if (recorder != null) recorder.filter(kind); + g.filter(kind); + } + + + public void filter(int kind, float param) { + if (recorder != null) recorder.filter(kind, param); + g.filter(kind, param); + } + + + public void copy(int sx, int sy, int sw, int sh, + int dx, int dy, int dw, int dh) { + if (recorder != null) recorder.copy(sx, sy, sw, sh, dx, dy, dw, dh); + g.copy(sx, sy, sw, sh, dx, dy, dw, dh); + } + + + public void copy(PImage src, + int sx, int sy, int sw, int sh, + int dx, int dy, int dw, int dh) { + if (recorder != null) recorder.copy(src, sx, sy, sw, sh, dx, dy, dw, dh); + g.copy(src, sx, sy, sw, sh, dx, dy, dw, dh); + } + + + static public int blendColor(int c1, int c2, int mode) { + return PGraphics.blendColor(c1, c2, mode); + } + + + public void blend(int sx, int sy, int sw, int sh, + int dx, int dy, int dw, int dh, int mode) { + if (recorder != null) recorder.blend(sx, sy, sw, sh, dx, dy, dw, dh, mode); + g.blend(sx, sy, sw, sh, dx, dy, dw, dh, mode); + } + + + public void blend(PImage src, + int sx, int sy, int sw, int sh, + int dx, int dy, int dw, int dh, int mode) { + if (recorder != null) recorder.blend(src, sx, sy, sw, sh, dx, dy, dw, dh, mode); + g.blend(src, sx, sy, sw, sh, dx, dy, dw, dh, mode); + } +} diff --git a/core/preproc/demo/PGraphics.java b/core/preproc/demo/PGraphics.java new file mode 100644 index 000000000..c3ff52828 --- /dev/null +++ b/core/preproc/demo/PGraphics.java @@ -0,0 +1,5043 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2004-08 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.core; + +import java.awt.*; +import java.util.HashMap; + + +/** + * Main graphics and rendering context, as well as the base API implementation. + * + *

Subclassing and initializing PGraphics objects

+ * Starting in release 0149, subclasses of PGraphics are handled differently. + * The constructor for subclasses takes no parameters, instead a series of + * functions are called by the hosting PApplet to specify its attributes. + * + * The functions were broken out because of the growing number of parameters + * such as these that might be used by a renderer, yet with the exception of + * setSize(), it's not clear which will be necessary. So while the size could + * be passed in to the constructor instead of a setSize() function, a function + * would still be needed that would notify the renderer that it was time to + * finish its initialization. Thus, setSize() simply does both. + * + *

Know your rights: public vs. private methods

+ * Methods that are protected are often subclassed by other renderers, however + * they are not set 'public' because they shouldn't be part of the user-facing + * public API accessible from PApplet. That is, we don't want sketches calling + * textModeCheck() or vertexTexture() directly. + * + *

Handling warnings and exceptions

+ * Methods that are unavailable generally show a warning, unless their lack of + * availability will soon cause another exception. For instance, if a method + * like getMatrix() returns null because it is unavailable, an exception will + * be thrown stating that the method is unavailable, rather than waiting for + * the NullPointerException that will occur when the sketch tries to use that + * method. As of release 0149, warnings will only be shown once, and exceptions + * have been changed to warnings where possible. + * + *

Using xxxxImpl() for subclassing smoothness

+ * The xxxImpl() methods are generally renderer-specific handling for some + * subset if tasks for a particular function (vague enough for you?) For + * instance, imageImpl() handles drawing an image whose x/y/w/h and u/v coords + * have been specified, and screen placement (independent of imageMode) has + * been determined. There's no point in all renderers implementing the + * if (imageMode == BLAH) placement/sizing logic, so that's handled + * by PGraphics, which then calls imageImpl() once all that is figured out. + * + *

His brother PImage

+ * PGraphics subclasses PImage so that it can be drawn and manipulated in a + * similar fashion. As such, many methods are inherited from PGraphics, + * though many are unavailable: for instance, resize() is not likely to be + * implemented; the same goes for mask(), depending on the situation. + * + *

What's in PGraphics, what ain't

+ * For the benefit of subclasses, as much as possible has been placed inside + * PGraphics. For instance, bezier interpolation code and implementations of + * the strokeCap() method (that simply sets the strokeCap variable) are + * handled here. Features that will vary widely between renderers are located + * inside the subclasses themselves. For instance, all matrix handling code + * is per-renderer: Java 2D uses its own AffineTransform, P2D uses a PMatrix2D, + * and PGraphics3D needs to keep continually update forward and reverse + * transformations. A proper (future) OpenGL implementation will have all its + * matrix madness handled by the card. Lighting also falls under this + * category, however the base material property settings (emissive, specular, + * et al.) are handled in PGraphics because they use the standard colorMode() + * logic. Subclasses should override methods like emissiveFromCalc(), which + * is a point where a valid color has been defined internally, and can be + * applied in some manner based on the calcXxxx values. + * + *

What's in the PGraphics documentation, what ain't

+ * Some things are noted here, some things are not. For public API, always + * refer to the reference + * on Processing.org for proper explanations. No attempt has been made to + * keep the javadoc up to date or complete. It's an enormous task for + * which we simply do not have the time. That is, it's not something that + * to be done once—it's a matter of keeping the multiple references + * synchronized (to say nothing of the translation issues), while targeting + * them for their separate audiences. Ouch. + */ +public class PGraphics extends PImage implements PConstants { + + // ........................................................ + + // width and height are already inherited from PImage + + + /// width minus one (useful for many calculations) + protected int width1; + + /// height minus one (useful for many calculations) + protected int height1; + + /// width * height (useful for many calculations) + public int pixelCount; + + /// true if smoothing is enabled (read-only) + public boolean smooth = false; + + // ........................................................ + + /// true if defaults() has been called a first time + protected boolean settingsInited; + + /// set to a PGraphics object being used inside a beginRaw/endRaw() block + protected PGraphics raw; + + // ........................................................ + + /** path to the file being saved for this renderer (if any) */ + protected String path; + + /** + * true if this is the main drawing surface for a particular sketch. + * This would be set to false for an offscreen buffer or if it were + * created any other way than size(). When this is set, the listeners + * are also added to the sketch. + */ + protected boolean primarySurface; + + // ........................................................ + + /** + * Array of hint[] items. These are hacks to get around various + * temporary workarounds inside the environment. + *

+ * Note that this array cannot be static, as a hint() may result in a + * runtime change specific to a renderer. For instance, calling + * hint(DISABLE_DEPTH_TEST) has to call glDisable() right away on an + * instance of PGraphicsOpenGL. + *

+ * The hints[] array is allocated early on because it might + * be used inside beginDraw(), allocate(), etc. + */ + protected boolean[] hints = new boolean[HINT_COUNT]; + + + //////////////////////////////////////////////////////////// + + // STYLE PROPERTIES + + // Also inherits imageMode() and smooth() (among others) from PImage. + + /** The current colorMode */ + public int colorMode; // = RGB; + + /** Max value for red (or hue) set by colorMode */ + public float colorModeX; // = 255; + + /** Max value for green (or saturation) set by colorMode */ + public float colorModeY; // = 255; + + /** Max value for blue (or value) set by colorMode */ + public float colorModeZ; // = 255; + + /** Max value for alpha set by colorMode */ + public float colorModeA; // = 255; + + /** True if colors are not in the range 0..1 */ + boolean colorModeScale; // = true; + + /** True if colorMode(RGB, 255) */ + boolean colorModeDefault; // = true; + + // ........................................................ + + // Tint color for images + + /** + * True if tint() is enabled (read-only). + * + * Using tint/tintColor seems a better option for naming than + * tintEnabled/tint because the latter seems ugly, even though + * g.tint as the actual color seems a little more intuitive, + * it's just that g.tintEnabled is even more unintuitive. + * Same goes for fill and stroke, et al. + */ + public boolean tint; + + /** tint that was last set (read-only) */ + public int tintColor; + + protected boolean tintAlpha; + protected float tintR, tintG, tintB, tintA; + protected int tintRi, tintGi, tintBi, tintAi; + + // ........................................................ + + // Fill color + + /** true if fill() is enabled, (read-only) */ + public boolean fill; + + /** fill that was last set (read-only) */ + public int fillColor = 0xffFFFFFF; + + protected boolean fillAlpha; + protected float fillR, fillG, fillB, fillA; + protected int fillRi, fillGi, fillBi, fillAi; + + // ........................................................ + + // Stroke color + + /** true if stroke() is enabled, (read-only) */ + public boolean stroke; + + /** stroke that was last set (read-only) */ + public int strokeColor = 0xff000000; + + protected boolean strokeAlpha; + protected float strokeR, strokeG, strokeB, strokeA; + protected int strokeRi, strokeGi, strokeBi, strokeAi; + + // ........................................................ + + // Additional stroke properties + + static protected final float DEFAULT_STROKE_WEIGHT = 1; + static protected final int DEFAULT_STROKE_JOIN = MITER; + static protected final int DEFAULT_STROKE_CAP = ROUND; + + /** + * Last value set by strokeWeight() (read-only). This has a default + * setting, rather than fighting with renderers about whether that + * renderer supports thick lines. + */ + public float strokeWeight = DEFAULT_STROKE_WEIGHT; + + /** + * Set by strokeJoin() (read-only). This has a default setting + * so that strokeJoin() need not be called by defaults, + * because subclasses may not implement it (i.e. PGraphicsGL) + */ + public int strokeJoin = DEFAULT_STROKE_JOIN; + + /** + * Set by strokeCap() (read-only). This has a default setting + * so that strokeCap() need not be called by defaults, + * because subclasses may not implement it (i.e. PGraphicsGL) + */ + public int strokeCap = DEFAULT_STROKE_CAP; + + // ........................................................ + + // Shape placement properties + + // imageMode() is inherited from PImage + + /** The current rect mode (read-only) */ + public int rectMode; + + /** The current ellipse mode (read-only) */ + public int ellipseMode; + + /** The current shape alignment mode (read-only) */ + public int shapeMode; + + /** The current image alignment (read-only) */ + public int imageMode = CORNER; + + // ........................................................ + + // Text and font properties + + /** The current text font (read-only) */ + public PFont textFont; + + /** The current text align (read-only) */ + public int textAlign = LEFT; + + /** The current vertical text alignment (read-only) */ + public int textAlignY = BASELINE; + + /** The current text mode (read-only) */ + public int textMode = MODEL; + + /** The current text size (read-only) */ + public float textSize; + + /** The current text leading (read-only) */ + public float textLeading; + + // ........................................................ + + // Material properties + +// PMaterial material; +// PMaterial[] materialStack; +// int materialStackPointer; + + public float ambientR, ambientG, ambientB; + public float specularR, specularG, specularB; + public float emissiveR, emissiveG, emissiveB; + public float shininess; + + + // Style stack + + static final int STYLE_STACK_DEPTH = 64; + PStyle[] styleStack = new PStyle[STYLE_STACK_DEPTH]; + int styleStackDepth; + + + //////////////////////////////////////////////////////////// + + + /** Last background color that was set, zero if an image */ + public int backgroundColor = 0xffCCCCCC; + + protected boolean backgroundAlpha; + protected float backgroundR, backgroundG, backgroundB, backgroundA; + protected int backgroundRi, backgroundGi, backgroundBi, backgroundAi; + + // ........................................................ + + /** + * Current model-view matrix transformation of the form m[row][column], + * which is a "column vector" (as opposed to "row vector") matrix. + */ +// PMatrix matrix; +// public float m00, m01, m02, m03; +// public float m10, m11, m12, m13; +// public float m20, m21, m22, m23; +// public float m30, m31, m32, m33; + +// static final int MATRIX_STACK_DEPTH = 32; +// float[][] matrixStack = new float[MATRIX_STACK_DEPTH][16]; +// float[][] matrixInvStack = new float[MATRIX_STACK_DEPTH][16]; +// int matrixStackDepth; + + static final int MATRIX_STACK_DEPTH = 32; + + // ........................................................ + + /** + * Java AWT Image object associated with this renderer. For P2D and P3D, + * this will be associated with their MemoryImageSource. For PGraphicsJava2D, + * it will be the offscreen drawing buffer. + */ + public Image image; + + // ........................................................ + + // internal color for setting/calculating + protected float calcR, calcG, calcB, calcA; + protected int calcRi, calcGi, calcBi, calcAi; + protected int calcColor; + protected boolean calcAlpha; + + /** The last RGB value converted to HSB */ + int cacheHsbKey; + /** Result of the last conversion to HSB */ + float[] cacheHsbValue = new float[3]; + + // ........................................................ + + /** + * Type of shape passed to beginShape(), + * zero if no shape is currently being drawn. + */ + protected int shape; + + // vertices + static final int DEFAULT_VERTICES = 512; + protected float vertices[][] = + new float[DEFAULT_VERTICES][VERTEX_FIELD_COUNT]; + protected int vertexCount; // total number of vertices + + // ........................................................ + + protected boolean bezierInited = false; + public int bezierDetail = 20; + + // used by both curve and bezier, so just init here + protected PMatrix3D bezierBasisMatrix = + new PMatrix3D(-1, 3, -3, 1, + 3, -6, 3, 0, + -3, 3, 0, 0, + 1, 0, 0, 0); + + //protected PMatrix3D bezierForwardMatrix; + protected PMatrix3D bezierDrawMatrix; + + // ........................................................ + + protected boolean curveInited = false; + protected int curveDetail = 20; + public float curveTightness = 0; + // catmull-rom basis matrix, perhaps with optional s parameter + protected PMatrix3D curveBasisMatrix; + protected PMatrix3D curveDrawMatrix; + + protected PMatrix3D bezierBasisInverse; + protected PMatrix3D curveToBezierMatrix; + + // ........................................................ + + // spline vertices + + protected float curveVertices[][]; + protected int curveVertexCount; + + // ........................................................ + + // precalculate sin/cos lookup tables [toxi] + // circle resolution is determined from the actual used radii + // passed to ellipse() method. this will automatically take any + // scale transformations into account too + + // [toxi 031031] + // changed table's precision to 0.5 degree steps + // introduced new vars for more flexible code + static final protected float sinLUT[]; + static final protected float cosLUT[]; + static final protected float SINCOS_PRECISION = 0.5f; + static final protected int SINCOS_LENGTH = (int) (360f / SINCOS_PRECISION); + static { + sinLUT = new float[SINCOS_LENGTH]; + cosLUT = new float[SINCOS_LENGTH]; + for (int i = 0; i < SINCOS_LENGTH; i++) { + sinLUT[i] = (float) Math.sin(i * DEG_TO_RAD * SINCOS_PRECISION); + cosLUT[i] = (float) Math.cos(i * DEG_TO_RAD * SINCOS_PRECISION); + } + } + + // ........................................................ + + /** The current font if a Java version of it is installed */ + //protected Font textFontNative; + + /** Metrics for the current native Java font */ + //protected FontMetrics textFontNativeMetrics; + + /** Last text position, because text often mixed on lines together */ + protected float textX, textY, textZ; + + /** + * Internal buffer used by the text() functions + * because the String object is slow + */ + protected char[] textBuffer = new char[8 * 1024]; + protected char[] textWidthBuffer = new char[8 * 1024]; + + protected int textBreakCount; + protected int[] textBreakStart; + protected int[] textBreakStop; + + // ........................................................ + + public boolean edge = true; + + // ........................................................ + + /// normal calculated per triangle + static protected final int NORMAL_MODE_AUTO = 0; + /// one normal manually specified per shape + static protected final int NORMAL_MODE_SHAPE = 1; + /// normals specified for each shape vertex + static protected final int NORMAL_MODE_VERTEX = 2; + + /// Current mode for normals, one of AUTO, SHAPE, or VERTEX + protected int normalMode; + + /// Keep track of how many calls to normal, to determine the mode. + //protected int normalCount; + + /** Current normal vector. */ + public float normalX, normalY, normalZ; + + // ........................................................ + + /** + * Sets whether texture coordinates passed to + * vertex() calls will be based on coordinates that are + * based on the IMAGE or NORMALIZED. + */ + public int textureMode; + + /** + * Current horizontal coordinate for texture, will always + * be between 0 and 1, even if using textureMode(IMAGE). + */ + public float textureU; + + /** Current vertical coordinate for texture, see above. */ + public float textureV; + + /** Current image being used as a texture */ + public PImage textureImage; + + // ........................................................ + + // [toxi031031] new & faster sphere code w/ support flexibile resolutions + // will be set by sphereDetail() or 1st call to sphere() + float sphereX[], sphereY[], sphereZ[]; + + /// Number of U steps (aka "theta") around longitudinally spanning 2*pi + public int sphereDetailU = 0; + /// Number of V steps (aka "phi") along latitudinally top-to-bottom spanning pi + public int sphereDetailV = 0; + + + ////////////////////////////////////////////////////////////// + + // INTERNAL + + + /** + * Constructor for the PGraphics object. Use this to ensure that + * the defaults get set properly. In a subclass, use this(w, h) + * as the first line of a subclass' constructor to properly set + * the internal fields and defaults. + */ + public PGraphics() { + } + + + public void setParent(PApplet parent) { // ignore + this.parent = parent; + } + + + /** + * Set (or unset) this as the main drawing surface. Meaning that it can + * safely be set to opaque (and given a default gray background), or anything + * else that goes along with that. + */ + public void setPrimary(boolean primary) { // ignore + this.primarySurface = primary; + + // base images must be opaque (for performance and general + // headache reasons.. argh, a semi-transparent opengl surface?) + // use createGraphics() if you want a transparent surface. + if (primarySurface) { + format = RGB; + } + } + + + public void setPath(String path) { // ignore + this.path = path; + } + + + /** + * The final step in setting up a renderer, set its size of this renderer. + * This was formerly handled by the constructor, but instead it's been broken + * out so that setParent/setPrimary/setPath can be handled differently. + * + * Important that this is ignored by preproc.pl because otherwise it will + * override setSize() in PApplet/Applet/Component, which will 1) not call + * super.setSize(), and 2) will cause the renderer to be resized from the + * event thread (EDT), causing a nasty crash as it collides with the + * animation thread. + */ + public void setSize(int w, int h) { // ignore + width = w; + height = h; + width1 = width - 1; + height1 = height - 1; + + allocate(); + reapplySettings(); + } + + + /** + * Allocate memory for this renderer. Generally will need to be implemented + * for all renderers. + */ + protected void allocate() { } + + + /** + * Handle any takedown for this graphics context. + *

+ * This is called when a sketch is shut down and this renderer was + * specified using the size() command, or inside endRecord() and + * endRaw(), in order to shut things off. + */ + public void dispose() { // ignore + } + + + + ////////////////////////////////////////////////////////////// + + // FRAME + + + /** + * Some renderers have requirements re: when they are ready to draw. + */ + public boolean canDraw() { // ignore + return true; + } + + + /** + * Prepares the PGraphics for drawing. + *

+ * When creating your own PGraphics, you should call this before + * drawing anything. + */ + public void beginDraw() { // ignore + } + + + /** + * This will finalize rendering so that it can be shown on-screen. + *

+ * When creating your own PGraphics, you should call this when + * you're finished drawing. + */ + public void endDraw() { // ignore + } + + + public void flush() { + // no-op, mostly for P3D to write sorted stuff + } + + + protected void checkSettings() { + if (!settingsInited) defaultSettings(); + } + + + /** + * Set engine's default values. This has to be called by PApplet, + * somewhere inside setup() or draw() because it talks to the + * graphics buffer, meaning that for subclasses like OpenGL, there + * needs to be a valid graphics context to mess with otherwise + * you'll get some good crashing action. + * + * This is currently called by checkSettings(), during beginDraw(). + */ + protected void defaultSettings() { // ignore +// System.out.println("PGraphics.defaultSettings() " + width + " " + height); + + noSmooth(); // 0149 + + colorMode(RGB, 255); + fill(255); + stroke(0); + // other stroke attributes are set in the initializers + // inside the class (see above, strokeWeight = 1 et al) + + // init shape stuff + shape = 0; + + // init matrices (must do before lights) + //matrixStackDepth = 0; + + rectMode(CORNER); + ellipseMode(DIAMETER); + + // no current font + textFont = null; + textSize = 12; + textLeading = 14; + textAlign = LEFT; + textMode = MODEL; + + // if this fella is associated with an applet, then clear its background. + // if it's been created by someone else through createGraphics, + // they have to call background() themselves, otherwise everything gets + // a gray background (when just a transparent surface or an empty pdf + // is what's desired). + // this background() call is for the Java 2D and OpenGL renderers. + if (primarySurface) { + //System.out.println("main drawing surface bg " + getClass().getName()); + background(backgroundColor); + } + + settingsInited = true; + // defaultSettings() overlaps reapplySettings(), don't do both + //reapplySettings = false; + } + + + /** + * Re-apply current settings. Some methods, such as textFont(), require that + * their methods be called (rather than simply setting the textFont variable) + * because they affect the graphics context, or they require parameters from + * the context (e.g. getting native fonts for text). + * + * This will only be called from an allocate(), which is only called from + * size(), which is safely called from inside beginDraw(). And it cannot be + * called before defaultSettings(), so we should be safe. + */ + protected void reapplySettings() { +// System.out.println("attempting reapplySettings()"); + if (!settingsInited) return; // if this is the initial setup, no need to reapply + +// System.out.println(" doing reapplySettings"); +// new Exception().printStackTrace(System.out); + + colorMode(colorMode, colorModeX, colorModeY, colorModeZ); + if (fill) { +// PApplet.println(" fill " + PApplet.hex(fillColor)); + fill(fillColor); + } else { + noFill(); + } + if (stroke) { + stroke(strokeColor); + + // The if() statements should be handled inside the functions, + // otherwise an actual reset/revert won't work properly. + //if (strokeWeight != DEFAULT_STROKE_WEIGHT) { + strokeWeight(strokeWeight); + //} +// if (strokeCap != DEFAULT_STROKE_CAP) { + strokeCap(strokeCap); +// } +// if (strokeJoin != DEFAULT_STROKE_JOIN) { + strokeJoin(strokeJoin); +// } + } else { + noStroke(); + } + if (tint) { + tint(tintColor); + } else { + noTint(); + } + if (smooth) { + smooth(); + } else { + // Don't bother setting this, cuz it'll anger P3D. + noSmooth(); + } + if (textFont != null) { +// System.out.println(" textFont in reapply is " + textFont); + // textFont() resets the leading, so save it in case it's changed + float saveLeading = textLeading; + textFont(textFont, textSize); + textLeading(saveLeading); + } + textMode(textMode); + textAlign(textAlign, textAlignY); + background(backgroundColor); + + //reapplySettings = false; + } + + + ////////////////////////////////////////////////////////////// + + // HINTS + + /** + * Enable a hint option. + *

+ * For the most part, hints are temporary api quirks, + * for which a proper api hasn't been properly worked out. + * for instance SMOOTH_IMAGES existed because smooth() + * wasn't yet implemented, but it will soon go away. + *

+ * They also exist for obscure features in the graphics + * engine, like enabling/disabling single pixel lines + * that ignore the zbuffer, the way they do in alphabot. + *

+ * Current hint options: + *

+ */ + public void hint(int which) { + if (which > 0) { + hints[which] = true; + } else { + hints[-which] = false; + } + } + + + + ////////////////////////////////////////////////////////////// + + // VERTEX SHAPES + + /** + * Start a new shape of type POLYGON + */ + public void beginShape() { + beginShape(POLYGON); + } + + + /** + * Start a new shape. + *

+ * Differences between beginShape() and line() and point() methods. + *

+ * beginShape() is intended to be more flexible at the expense of being + * a little more complicated to use. it handles more complicated shapes + * that can consist of many connected lines (so you get joins) or lines + * mixed with curves. + *

+ * The line() and point() command are for the far more common cases + * (particularly for our audience) that simply need to draw a line + * or a point on the screen. + *

+ * From the code side of things, line() may or may not call beginShape() + * to do the drawing. In the beta code, they do, but in the alpha code, + * they did not. they might be implemented one way or the other depending + * on tradeoffs of runtime efficiency vs. implementation efficiency &mdash + * meaning the speed that things run at vs. the speed it takes me to write + * the code and maintain it. for beta, the latter is most important so + * that's how things are implemented. + */ + public void beginShape(int kind) { + shape = kind; + } + + + /** + * Sets whether the upcoming vertex is part of an edge. + * Equivalent to glEdgeFlag(), for people familiar with OpenGL. + */ + public void edge(boolean edge) { + this.edge = edge; + } + + + /** + * Sets the current normal vector. Only applies with 3D rendering + * and inside a beginShape/endShape block. + *

+ * This is for drawing three dimensional shapes and surfaces, + * allowing you to specify a vector perpendicular to the surface + * of the shape, which determines how lighting affects it. + *

+ * For the most part, PGraphics3D will attempt to automatically + * assign normals to shapes, but since that's imperfect, + * this is a better option when you want more control. + *

+ * For people familiar with OpenGL, this function is basically + * identical to glNormal3f(). + */ + public void normal(float nx, float ny, float nz) { + normalX = nx; + normalY = ny; + normalZ = nz; + + // if drawing a shape and the normal hasn't been set yet, + // then we need to set the normals for each vertex so far + if (shape != 0) { + if (normalMode == NORMAL_MODE_AUTO) { + // either they set the normals, or they don't [0149] +// for (int i = vertex_start; i < vertexCount; i++) { +// vertices[i][NX] = normalX; +// vertices[i][NY] = normalY; +// vertices[i][NZ] = normalZ; +// } + // One normal per begin/end shape + normalMode = NORMAL_MODE_SHAPE; + + } else if (normalMode == NORMAL_MODE_SHAPE) { + // a separate normal for each vertex + normalMode = NORMAL_MODE_VERTEX; + } + } + } + + + /** + * Set texture mode to either to use coordinates based on the IMAGE + * (more intuitive for new users) or NORMALIZED (better for advanced chaps) + */ + public void textureMode(int mode) { + this.textureMode = mode; + } + + + /** + * Set texture image for current shape. + * Needs to be called between @see beginShape and @see endShape + * + * @param image reference to a PImage object + */ + public void texture(PImage image) { + textureImage = image; + } + + + protected void vertexCheck() { + if (vertexCount == vertices.length) { + float temp[][] = new float[vertexCount << 1][VERTEX_FIELD_COUNT]; + System.arraycopy(vertices, 0, temp, 0, vertexCount); + vertices = temp; + } + } + + + public void vertex(float x, float y) { + vertexCheck(); + float[] vertex = vertices[vertexCount]; + + curveVertexCount = 0; + + vertex[X] = x; + vertex[Y] = y; + + vertex[EDGE] = edge ? 1 : 0; + +// if (fill) { +// vertex[R] = fillR; +// vertex[G] = fillG; +// vertex[B] = fillB; +// vertex[A] = fillA; +// } + if (fill || textureImage != null) { + if (textureImage == null) { + vertex[R] = fillR; + vertex[G] = fillG; + vertex[B] = fillB; + vertex[A] = fillA; + } else { + if (tint) { + vertex[R] = tintR; + vertex[G] = tintG; + vertex[B] = tintB; + vertex[A] = tintA; + } else { + vertex[R] = 1; + vertex[G] = 1; + vertex[B] = 1; + vertex[A] = 1; + } + } + } + + if (stroke) { + vertex[SR] = strokeR; + vertex[SG] = strokeG; + vertex[SB] = strokeB; + vertex[SA] = strokeA; + vertex[SW] = strokeWeight; + } + + if (textureImage != null) { + vertex[U] = textureU; + vertex[V] = textureV; + } + + vertexCount++; + } + + + public void vertex(float x, float y, float z) { + vertexCheck(); + float[] vertex = vertices[vertexCount]; + + // only do this if we're using an irregular (POLYGON) shape that + // will go through the triangulator. otherwise it'll do thinks like + // disappear in mathematically odd ways + // http://dev.processing.org/bugs/show_bug.cgi?id=444 + if (shape == POLYGON) { + if (vertexCount > 0) { + float pvertex[] = vertices[vertexCount-1]; + if ((Math.abs(pvertex[X] - x) < EPSILON) && + (Math.abs(pvertex[Y] - y) < EPSILON) && + (Math.abs(pvertex[Z] - z) < EPSILON)) { + // this vertex is identical, don't add it, + // because it will anger the triangulator + return; + } + } + } + + // User called vertex(), so that invalidates anything queued up for curve + // vertices. If this is internally called by curveVertexSegment, + // then curveVertexCount will be saved and restored. + curveVertexCount = 0; + + vertex[X] = x; + vertex[Y] = y; + vertex[Z] = z; + + vertex[EDGE] = edge ? 1 : 0; + + if (fill || textureImage != null) { + if (textureImage == null) { + vertex[R] = fillR; + vertex[G] = fillG; + vertex[B] = fillB; + vertex[A] = fillA; + } else { + if (tint) { + vertex[R] = tintR; + vertex[G] = tintG; + vertex[B] = tintB; + vertex[A] = tintA; + } else { + vertex[R] = 1; + vertex[G] = 1; + vertex[B] = 1; + vertex[A] = 1; + } + } + + vertex[AR] = ambientR; + vertex[AG] = ambientG; + vertex[AB] = ambientB; + + vertex[SPR] = specularR; + vertex[SPG] = specularG; + vertex[SPB] = specularB; + //vertex[SPA] = specularA; + + vertex[SHINE] = shininess; + + vertex[ER] = emissiveR; + vertex[EG] = emissiveG; + vertex[EB] = emissiveB; + } + + if (stroke) { + vertex[SR] = strokeR; + vertex[SG] = strokeG; + vertex[SB] = strokeB; + vertex[SA] = strokeA; + vertex[SW] = strokeWeight; + } + + if (textureImage != null) { + vertex[U] = textureU; + vertex[V] = textureV; + } + + vertex[NX] = normalX; + vertex[NY] = normalY; + vertex[NZ] = normalZ; + + vertex[BEEN_LIT] = 0; + + vertexCount++; + } + + + /** + * Used by renderer subclasses or PShape to efficiently pass in already + * formatted vertex information. + * @param v vertex parameters, as a float array of length VERTEX_FIELD_COUNT + */ + public void vertex(float[] v) { + vertexCheck(); + curveVertexCount = 0; + float[] vertex = vertices[vertexCount]; + System.arraycopy(v, 0, vertex, 0, VERTEX_FIELD_COUNT); + vertexCount++; + } + + + public void vertex(float x, float y, float u, float v) { + vertexTexture(u, v); + vertex(x, y); + } + + + public void vertex(float x, float y, float z, float u, float v) { + vertexTexture(u, v); + vertex(x, y, z); + } + + + /** + * Internal method to copy all style information for the given vertex. + * Can be overridden by subclasses to handle only properties pertinent to + * that renderer. (e.g. no need to copy the emissive color in P2D) + */ +// protected void vertexStyle() { +// } + + + /** + * Set (U, V) coords for the next vertex in the current shape. + * This is ugly as its own function, and will (almost?) always be + * coincident with a call to vertex. As of beta, this was moved to + * the protected method you see here, and called from an optional + * param of and overloaded vertex(). + *

+ * The parameters depend on the current textureMode. When using + * textureMode(IMAGE), the coordinates will be relative to the size + * of the image texture, when used with textureMode(NORMAL), + * they'll be in the range 0..1. + *

+ * Used by both PGraphics2D (for images) and PGraphics3D. + */ + protected void vertexTexture(float u, float v) { + if (textureImage == null) { + throw new RuntimeException("You must first call texture() before " + + "using u and v coordinates with vertex()"); + } + if (textureMode == IMAGE) { + u /= (float) textureImage.width; + v /= (float) textureImage.height; + } + + textureU = u; + textureV = v; + + if (textureU < 0) textureU = 0; + else if (textureU > 1) textureU = 1; + + if (textureV < 0) textureV = 0; + else if (textureV > 1) textureV = 1; + } + + + /** This feature is in testing, do not use or rely upon its implementation */ + public void breakShape() { + showWarning("This renderer cannot currently handle concave shapes, " + + "or shapes with holes."); + } + + + public void endShape() { + endShape(OPEN); + } + + + public void endShape(int mode) { + } + + + + ////////////////////////////////////////////////////////////// + + // CURVE/BEZIER VERTEX HANDLING + + + protected void bezierVertexCheck() { + if (shape == 0 || shape != POLYGON) { + throw new RuntimeException("beginShape() or beginShape(POLYGON) " + + "must be used before bezierVertex()"); + } + if (vertexCount == 0) { + throw new RuntimeException("vertex() must be used at least once" + + "before bezierVertex()"); + } + } + + + public void bezierVertex(float x2, float y2, + float x3, float y3, + float x4, float y4) { + bezierInitCheck(); + bezierVertexCheck(); + PMatrix3D draw = bezierDrawMatrix; + + float[] prev = vertices[vertexCount-1]; + float x1 = prev[X]; + float y1 = prev[Y]; + + float xplot1 = draw.m10*x1 + draw.m11*x2 + draw.m12*x3 + draw.m13*x4; + float xplot2 = draw.m20*x1 + draw.m21*x2 + draw.m22*x3 + draw.m23*x4; + float xplot3 = draw.m30*x1 + draw.m31*x2 + draw.m32*x3 + draw.m33*x4; + + float yplot1 = draw.m10*y1 + draw.m11*y2 + draw.m12*y3 + draw.m13*y4; + float yplot2 = draw.m20*y1 + draw.m21*y2 + draw.m22*y3 + draw.m23*y4; + float yplot3 = draw.m30*y1 + draw.m31*y2 + draw.m32*y3 + draw.m33*y4; + + for (int j = 0; j < bezierDetail; j++) { + x1 += xplot1; xplot1 += xplot2; xplot2 += xplot3; + y1 += yplot1; yplot1 += yplot2; yplot2 += yplot3; + vertex(x1, y1); + } + } + + + public void bezierVertex(float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + bezierInitCheck(); + bezierVertexCheck(); + PMatrix3D draw = bezierDrawMatrix; + + float[] prev = vertices[vertexCount-1]; + float x1 = prev[X]; + float y1 = prev[Y]; + float z1 = prev[Z]; + + float xplot1 = draw.m10*x1 + draw.m11*x2 + draw.m12*x3 + draw.m13*x4; + float xplot2 = draw.m20*x1 + draw.m21*x2 + draw.m22*x3 + draw.m23*x4; + float xplot3 = draw.m30*x1 + draw.m31*x2 + draw.m32*x3 + draw.m33*x4; + + float yplot1 = draw.m10*y1 + draw.m11*y2 + draw.m12*y3 + draw.m13*y4; + float yplot2 = draw.m20*y1 + draw.m21*y2 + draw.m22*y3 + draw.m23*y4; + float yplot3 = draw.m30*y1 + draw.m31*y2 + draw.m32*y3 + draw.m33*y4; + + float zplot1 = draw.m10*z1 + draw.m11*z2 + draw.m12*z3 + draw.m13*z4; + float zplot2 = draw.m20*z1 + draw.m21*z2 + draw.m22*z3 + draw.m23*z4; + float zplot3 = draw.m30*z1 + draw.m31*z2 + draw.m32*z3 + draw.m33*z4; + + for (int j = 0; j < bezierDetail; j++) { + x1 += xplot1; xplot1 += xplot2; xplot2 += xplot3; + y1 += yplot1; yplot1 += yplot2; yplot2 += yplot3; + z1 += zplot1; zplot1 += zplot2; zplot2 += zplot3; + vertex(x1, y1, z1); + } + } + + + /** + * Perform initialization specific to curveVertex(), and handle standard + * error modes. Can be overridden by subclasses that need the flexibility. + */ + protected void curveVertexCheck() { + if (shape != POLYGON) { + throw new RuntimeException("You must use beginShape() or " + + "beginShape(POLYGON) before curveVertex()"); + } + // to improve code init time, allocate on first use. + if (curveVertices == null) { + curveVertices = new float[128][3]; + } + + if (curveVertexCount == curveVertices.length) { + // Can't use PApplet.expand() cuz it doesn't do the copy properly + float[][] temp = new float[curveVertexCount << 1][3]; + System.arraycopy(curveVertices, 0, temp, 0, curveVertexCount); + curveVertices = temp; + } + curveInitCheck(); + } + + + public void curveVertex(float x, float y) { + curveVertexCheck(); + float[] vertex = curveVertices[curveVertexCount]; + vertex[X] = x; + vertex[Y] = y; + curveVertexCount++; + + // draw a segment if there are enough points + if (curveVertexCount > 3) { + curveVertexSegment(curveVertices[curveVertexCount-4][X], + curveVertices[curveVertexCount-4][Y], + curveVertices[curveVertexCount-3][X], + curveVertices[curveVertexCount-3][Y], + curveVertices[curveVertexCount-2][X], + curveVertices[curveVertexCount-2][Y], + curveVertices[curveVertexCount-1][X], + curveVertices[curveVertexCount-1][Y]); + } + } + + + public void curveVertex(float x, float y, float z) { + curveVertexCheck(); + float[] vertex = curveVertices[curveVertexCount]; + vertex[X] = x; + vertex[Y] = y; + vertex[Z] = z; + curveVertexCount++; + + // draw a segment if there are enough points + if (curveVertexCount > 3) { + curveVertexSegment(curveVertices[curveVertexCount-4][X], + curveVertices[curveVertexCount-4][Y], + curveVertices[curveVertexCount-4][Z], + curveVertices[curveVertexCount-3][X], + curveVertices[curveVertexCount-3][Y], + curveVertices[curveVertexCount-3][Z], + curveVertices[curveVertexCount-2][X], + curveVertices[curveVertexCount-2][Y], + curveVertices[curveVertexCount-2][Z], + curveVertices[curveVertexCount-1][X], + curveVertices[curveVertexCount-1][Y], + curveVertices[curveVertexCount-1][Z]); + } + } + + + /** + * Handle emitting a specific segment of Catmull-Rom curve. This can be + * overridden by subclasses that need more efficient rendering options. + */ + protected void curveVertexSegment(float x1, float y1, + float x2, float y2, + float x3, float y3, + float x4, float y4) { + float x0 = x2; + float y0 = y2; + + PMatrix3D draw = curveDrawMatrix; + + float xplot1 = draw.m10*x1 + draw.m11*x2 + draw.m12*x3 + draw.m13*x4; + float xplot2 = draw.m20*x1 + draw.m21*x2 + draw.m22*x3 + draw.m23*x4; + float xplot3 = draw.m30*x1 + draw.m31*x2 + draw.m32*x3 + draw.m33*x4; + + float yplot1 = draw.m10*y1 + draw.m11*y2 + draw.m12*y3 + draw.m13*y4; + float yplot2 = draw.m20*y1 + draw.m21*y2 + draw.m22*y3 + draw.m23*y4; + float yplot3 = draw.m30*y1 + draw.m31*y2 + draw.m32*y3 + draw.m33*y4; + + // vertex() will reset splineVertexCount, so save it + int savedCount = curveVertexCount; + + vertex(x0, y0); + for (int j = 0; j < curveDetail; j++) { + x0 += xplot1; xplot1 += xplot2; xplot2 += xplot3; + y0 += yplot1; yplot1 += yplot2; yplot2 += yplot3; + vertex(x0, y0); + } + curveVertexCount = savedCount; + } + + + /** + * Handle emitting a specific segment of Catmull-Rom curve. This can be + * overridden by subclasses that need more efficient rendering options. + */ + protected void curveVertexSegment(float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + float x0 = x2; + float y0 = y2; + float z0 = z2; + + PMatrix3D draw = curveDrawMatrix; + + float xplot1 = draw.m10*x1 + draw.m11*x2 + draw.m12*x3 + draw.m13*x4; + float xplot2 = draw.m20*x1 + draw.m21*x2 + draw.m22*x3 + draw.m23*x4; + float xplot3 = draw.m30*x1 + draw.m31*x2 + draw.m32*x3 + draw.m33*x4; + + float yplot1 = draw.m10*y1 + draw.m11*y2 + draw.m12*y3 + draw.m13*y4; + float yplot2 = draw.m20*y1 + draw.m21*y2 + draw.m22*y3 + draw.m23*y4; + float yplot3 = draw.m30*y1 + draw.m31*y2 + draw.m32*y3 + draw.m33*y4; + + // vertex() will reset splineVertexCount, so save it + int savedCount = curveVertexCount; + + float zplot1 = draw.m10*z1 + draw.m11*z2 + draw.m12*z3 + draw.m13*z4; + float zplot2 = draw.m20*z1 + draw.m21*z2 + draw.m22*z3 + draw.m23*z4; + float zplot3 = draw.m30*z1 + draw.m31*z2 + draw.m32*z3 + draw.m33*z4; + + vertex(x0, y0, z0); + for (int j = 0; j < curveDetail; j++) { + x0 += xplot1; xplot1 += xplot2; xplot2 += xplot3; + y0 += yplot1; yplot1 += yplot2; yplot2 += yplot3; + z0 += zplot1; zplot1 += zplot2; zplot2 += zplot3; + vertex(x0, y0, z0); + } + curveVertexCount = savedCount; + } + + + + ////////////////////////////////////////////////////////////// + + // SIMPLE SHAPES WITH ANALOGUES IN beginShape() + + + public void point(float x, float y) { + beginShape(POINTS); + vertex(x, y); + endShape(); + } + + + public void point(float x, float y, float z) { + beginShape(POINTS); + vertex(x, y, z); + endShape(); + } + + + public void line(float x1, float y1, float x2, float y2) { + beginShape(LINES); + vertex(x1, y1); + vertex(x2, y2); + endShape(); + } + + + public void line(float x1, float y1, float z1, + float x2, float y2, float z2) { + beginShape(LINES); + vertex(x1, y1, z1); + vertex(x2, y2, z2); + endShape(); + } + + + public void triangle(float x1, float y1, float x2, float y2, + float x3, float y3) { + beginShape(TRIANGLES); + vertex(x1, y1); + vertex(x2, y2); + vertex(x3, y3); + endShape(); + } + + + public void quad(float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4) { + beginShape(QUADS); + vertex(x1, y1); + vertex(x2, y2); + vertex(x3, y3); + vertex(x4, y4); + endShape(); + } + + + + ////////////////////////////////////////////////////////////// + + // RECT + + + public void rectMode(int mode) { + rectMode = mode; + } + + + public void rect(float a, float b, float c, float d) { + float hradius, vradius; + switch (rectMode) { + case CORNERS: + break; + case CORNER: + c += a; d += b; + break; + case RADIUS: + hradius = c; + vradius = d; + c = a + hradius; + d = b + vradius; + a -= hradius; + b -= vradius; + break; + case CENTER: + hradius = c / 2.0f; + vradius = d / 2.0f; + c = a + hradius; + d = b + vradius; + a -= hradius; + b -= vradius; + } + + if (a > c) { + float temp = a; a = c; c = temp; + } + + if (b > d) { + float temp = b; b = d; d = temp; + } + + rectImpl(a, b, c, d); + } + + + protected void rectImpl(float x1, float y1, float x2, float y2) { + quad(x1, y1, x2, y1, x2, y2, x1, y2); + } + + + + ////////////////////////////////////////////////////////////// + + // ELLIPSE AND ARC + + + public void ellipseMode(int mode) { + ellipseMode = mode; + } + + + public void ellipse(float a, float b, float c, float d) { + float x = a; + float y = b; + float w = c; + float h = d; + + if (ellipseMode == CORNERS) { + w = c - a; + h = d - b; + + } else if (ellipseMode == RADIUS) { + x = a - c; + y = b - d; + w = c * 2; + h = d * 2; + + } else if (ellipseMode == DIAMETER) { + x = a - c/2f; + y = b - d/2f; + } + + if (w < 0) { // undo negative width + x += w; + w = -w; + } + + if (h < 0) { // undo negative height + y += h; + h = -h; + } + + ellipseImpl(x, y, w, h); + } + + + protected void ellipseImpl(float x, float y, float w, float h) { + } + + + /** + * Identical parameters and placement to ellipse, + * but draws only an arc of that ellipse. + *

+ * start and stop are always radians because angleMode() was goofy. + * ellipseMode() sets the placement. + *

+ * also tries to be smart about start < stop. + */ + public void arc(float a, float b, float c, float d, + float start, float stop) { + float x = a; + float y = b; + float w = c; + float h = d; + + if (ellipseMode == CORNERS) { + w = c - a; + h = d - b; + + } else if (ellipseMode == RADIUS) { + x = a - c; + y = b - d; + w = c * 2; + h = d * 2; + + } else if (ellipseMode == CENTER) { + x = a - c/2f; + y = b - d/2f; + } + + // make sure this loop will exit before starting while + if (Float.isInfinite(start) || Float.isInfinite(stop)) return; +// while (stop < start) stop += TWO_PI; + if (stop < start) return; // why bother + + // make sure that we're starting at a useful point + while (start < 0) { + start += TWO_PI; + stop += TWO_PI; + } + + if (stop - start > TWO_PI) { + start = 0; + stop = TWO_PI; + } + + arcImpl(x, y, w, h, start, stop); + } + + + /** + * Start and stop are in radians, converted by the parent function. + * Note that the radians can be greater (or less) than TWO_PI. + * This is so that an arc can be drawn that crosses zero mark, + * and the user will still collect $200. + */ + protected void arcImpl(float x, float y, float w, float h, + float start, float stop) { + } + + + + ////////////////////////////////////////////////////////////// + + // BOX + + + public void box(float size) { + box(size, size, size); + } + + + // TODO not the least bit efficient, it even redraws lines + // along the vertices. ugly ugly ugly! + public void box(float w, float h, float d) { + float x1 = -w/2f; float x2 = w/2f; + float y1 = -h/2f; float y2 = h/2f; + float z1 = -d/2f; float z2 = d/2f; + + beginShape(QUADS); + + // front + normal(0, 0, 1); + vertex(x1, y1, z1); + vertex(x2, y1, z1); + vertex(x2, y2, z1); + vertex(x1, y2, z1); + + // right + normal(1, 0, 0); + vertex(x2, y1, z1); + vertex(x2, y1, z2); + vertex(x2, y2, z2); + vertex(x2, y2, z1); + + // back + normal(0, 0, -1); + vertex(x2, y1, z2); + vertex(x1, y1, z2); + vertex(x1, y2, z2); + vertex(x2, y2, z2); + + // left + normal(-1, 0, 0); + vertex(x1, y1, z2); + vertex(x1, y1, z1); + vertex(x1, y2, z1); + vertex(x1, y2, z2); + + // top + normal(0, 1, 0); + vertex(x1, y1, z2); + vertex(x2, y1, z2); + vertex(x2, y1, z1); + vertex(x1, y1, z1); + + // bottom + normal(0, -1, 0); + vertex(x1, y2, z1); + vertex(x2, y2, z1); + vertex(x2, y2, z2); + vertex(x1, y2, z2); + + endShape(); + } + + + + ////////////////////////////////////////////////////////////// + + // SPHERE + + + public void sphereDetail(int res) { + sphereDetail(res, res); + } + + + /** + * Set the detail level for approximating a sphere. The ures and vres params + * control the horizontal and vertical resolution. + * + * Code for sphereDetail() submitted by toxi [031031]. + * Code for enhanced u/v version from davbol [080801]. + */ + public void sphereDetail(int ures, int vres) { + if (ures < 3) ures = 3; // force a minimum res + if (vres < 2) vres = 2; // force a minimum res + if ((ures == sphereDetailU) && (vres == sphereDetailV)) return; + + float delta = (float)SINCOS_LENGTH/ures; + float[] cx = new float[ures]; + float[] cz = new float[ures]; + // calc unit circle in XZ plane + for (int i = 0; i < ures; i++) { + cx[i] = cosLUT[(int) (i*delta) % SINCOS_LENGTH]; + cz[i] = sinLUT[(int) (i*delta) % SINCOS_LENGTH]; + } + // computing vertexlist + // vertexlist starts at south pole + int vertCount = ures * (vres-1) + 2; + int currVert = 0; + + // re-init arrays to store vertices + sphereX = new float[vertCount]; + sphereY = new float[vertCount]; + sphereZ = new float[vertCount]; + + float angle_step = (SINCOS_LENGTH*0.5f)/vres; + float angle = angle_step; + + // step along Y axis + for (int i = 1; i < vres; i++) { + float curradius = sinLUT[(int) angle % SINCOS_LENGTH]; + float currY = -cosLUT[(int) angle % SINCOS_LENGTH]; + for (int j = 0; j < ures; j++) { + sphereX[currVert] = cx[j] * curradius; + sphereY[currVert] = currY; + sphereZ[currVert++] = cz[j] * curradius; + } + angle += angle_step; + } + sphereDetailU = ures; + sphereDetailV = vres; + } + + + /** + * Draw a sphere with radius r centered at coordinate 0, 0, 0. + *

+ * Implementation notes: + *

+ * cache all the points of the sphere in a static array + * top and bottom are just a bunch of triangles that land + * in the center point + *

+ * sphere is a series of concentric circles who radii vary + * along the shape, based on, er.. cos or something + *

+   * [toxi 031031] new sphere code. removed all multiplies with
+   * radius, as scale() will take care of that anyway
+   *
+   * [toxi 031223] updated sphere code (removed modulos)
+   * and introduced sphereAt(x,y,z,r)
+   * to avoid additional translate()'s on the user/sketch side
+   *
+   * [davbol 080801] now using separate sphereDetailU/V
+   * 
+ */ + public void sphere(float r) { + if ((sphereDetailU < 3) || (sphereDetailV < 2)) { + sphereDetail(30); + } + + pushMatrix(); + scale(r); + edge(false); + + // 1st ring from south pole + beginShape(TRIANGLE_STRIP); + for (int i = 0; i < sphereDetailU; i++) { + normal(0, -1, 0); + vertex(0, -1, 0); + normal(sphereX[i], sphereY[i], sphereZ[i]); + vertex(sphereX[i], sphereY[i], sphereZ[i]); + } + //normal(0, -1, 0); + vertex(0, -1, 0); + normal(sphereX[0], sphereY[0], sphereZ[0]); + vertex(sphereX[0], sphereY[0], sphereZ[0]); + endShape(); + + int v1,v11,v2; + + // middle rings + int voff = 0; + for (int i = 2; i < sphereDetailV; i++) { + v1 = v11 = voff; + voff += sphereDetailU; + v2 = voff; + beginShape(TRIANGLE_STRIP); + for (int j = 0; j < sphereDetailU; j++) { + normal(sphereX[v1], sphereY[v1], sphereZ[v1]); + vertex(sphereX[v1], sphereY[v1], sphereZ[v1++]); + normal(sphereX[v2], sphereY[v2], sphereZ[v2]); + vertex(sphereX[v2], sphereY[v2], sphereZ[v2++]); + } + // close each ring + v1 = v11; + v2 = voff; + normal(sphereX[v1], sphereY[v1], sphereZ[v1]); + vertex(sphereX[v1], sphereY[v1], sphereZ[v1]); + normal(sphereX[v2], sphereY[v2], sphereZ[v2]); + vertex(sphereX[v2], sphereY[v2], sphereZ[v2]); + endShape(); + } + + // add the northern cap + beginShape(TRIANGLE_STRIP); + for (int i = 0; i < sphereDetailU; i++) { + v2 = voff + i; + normal(sphereX[v2], sphereY[v2], sphereZ[v2]); + vertex(sphereX[v2], sphereY[v2], sphereZ[v2]); + normal(0, 1, 0); + vertex(0, 1, 0); + } + normal(sphereX[voff], sphereY[voff], sphereZ[voff]); + vertex(sphereX[voff], sphereY[voff], sphereZ[voff]); + normal(0, 1, 0); + vertex(0, 1, 0); + endShape(); + + edge(true); + popMatrix(); + } + + + + ////////////////////////////////////////////////////////////// + + // BEZIER + + + /** + * Evalutes quadratic bezier at point t for points a, b, c, d. + * t varies between 0 and 1, and a and d are the on curve points, + * b and c are the control points. this can be done once with the + * x coordinates and a second time with the y coordinates to get + * the location of a bezier curve at t. + *

+ * For instance, to convert the following example:

+   * stroke(255, 102, 0);
+   * line(85, 20, 10, 10);
+   * line(90, 90, 15, 80);
+   * stroke(0, 0, 0);
+   * bezier(85, 20, 10, 10, 90, 90, 15, 80);
+   *
+   * // draw it in gray, using 10 steps instead of the default 20
+   * // this is a slower way to do it, but useful if you need
+   * // to do things with the coordinates at each step
+   * stroke(128);
+   * beginShape(LINE_STRIP);
+   * for (int i = 0; i <= 10; i++) {
+   *   float t = i / 10.0f;
+   *   float x = bezierPoint(85, 10, 90, 15, t);
+   *   float y = bezierPoint(20, 10, 90, 80, t);
+   *   vertex(x, y);
+   * }
+   * endShape();
+ */ + public float bezierPoint(float a, float b, float c, float d, float t) { + float t1 = 1.0f - t; + return a*t1*t1*t1 + 3*b*t*t1*t1 + 3*c*t*t*t1 + d*t*t*t; + } + + + /** + * Provide the tangent at the given point on the bezier curve. + * Fix from davbol for 0136. + */ + public float bezierTangent(float a, float b, float c, float d, float t) { + return (3*t*t * (-a+3*b-3*c+d) + + 6*t * (a-2*b+c) + + 3 * (-a+b)); + } + + + protected void bezierInitCheck() { + if (!bezierInited) { + bezierInit(); + } + } + + + protected void bezierInit() { + // overkill to be broken out, but better parity with the curve stuff below + bezierDetail(bezierDetail); + bezierInited = true; + } + + + public void bezierDetail(int detail) { + bezierDetail = detail; + + if (bezierDrawMatrix == null) { + bezierDrawMatrix = new PMatrix3D(); + } + + // setup matrix for forward differencing to speed up drawing + splineForward(detail, bezierDrawMatrix); + + // multiply the basis and forward diff matrices together + // saves much time since this needn't be done for each curve + //mult_spline_matrix(bezierForwardMatrix, bezier_basis, bezierDrawMatrix, 4); + //bezierDrawMatrix.set(bezierForwardMatrix); + bezierDrawMatrix.apply(bezierBasisMatrix); + } + + + /** + * Draw a cubic bezier curve. The first and last points are + * the on-curve points. The middle two are the 'control' points, + * or 'handles' in an application like Illustrator. + *

+ * Identical to typing: + *

beginShape();
+   * vertex(x1, y1);
+   * bezierVertex(x2, y2, x3, y3, x4, y4);
+   * endShape();
+   * 
+ * In Postscript-speak, this would be: + *
moveto(x1, y1);
+   * curveto(x2, y2, x3, y3, x4, y4);
+ * If you were to try and continue that curve like so: + *
curveto(x5, y5, x6, y6, x7, y7);
+ * This would be done in processing by adding these statements: + *
bezierVertex(x5, y5, x6, y6, x7, y7)
+   * 
+ * To draw a quadratic (instead of cubic) curve, + * use the control point twice by doubling it: + *
bezier(x1, y1, cx, cy, cx, cy, x2, y2);
+ */ + public void bezier(float x1, float y1, + float x2, float y2, + float x3, float y3, + float x4, float y4) { + beginShape(); + vertex(x1, y1); + bezierVertex(x2, y2, x3, y3, x4, y4); + endShape(); + } + + + public void bezier(float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + beginShape(); + vertex(x1, y1, z1); + bezierVertex(x2, y2, z2, + x3, y3, z3, + x4, y4, z4); + endShape(); + } + + + + ////////////////////////////////////////////////////////////// + + // CATMULL-ROM CURVE + + + /** + * Get a location along a catmull-rom curve segment. + * + * @param t Value between zero and one for how far along the segment + */ + public float curvePoint(float a, float b, float c, float d, float t) { + curveInitCheck(); + + float tt = t * t; + float ttt = t * tt; + PMatrix3D cb = curveBasisMatrix; + + // not optimized (and probably need not be) + return (a * (ttt*cb.m00 + tt*cb.m10 + t*cb.m20 + cb.m30) + + b * (ttt*cb.m01 + tt*cb.m11 + t*cb.m21 + cb.m31) + + c * (ttt*cb.m02 + tt*cb.m12 + t*cb.m22 + cb.m32) + + d * (ttt*cb.m03 + tt*cb.m13 + t*cb.m23 + cb.m33)); + } + + + /** + * Calculate the tangent at a t value (0..1) on a Catmull-Rom curve. + * Code thanks to Dave Bollinger (Bug #715) + */ + public float curveTangent(float a, float b, float c, float d, float t) { + curveInitCheck(); + + float tt3 = t * t * 3; + float t2 = t * 2; + PMatrix3D cb = curveBasisMatrix; + + // not optimized (and probably need not be) + return (a * (tt3*cb.m00 + t2*cb.m10 + cb.m20) + + b * (tt3*cb.m01 + t2*cb.m11 + cb.m21) + + c * (tt3*cb.m02 + t2*cb.m12 + cb.m22) + + d * (tt3*cb.m03 + t2*cb.m13 + cb.m23) ); + } + + + public void curveDetail(int detail) { + curveDetail = detail; + curveInit(); + } + + + public void curveTightness(float tightness) { + curveTightness = tightness; + curveInit(); + } + + + protected void curveInitCheck() { + if (!curveInited) { + curveInit(); + } + } + + + /** + * Set the number of segments to use when drawing a Catmull-Rom + * curve, and setting the s parameter, which defines how tightly + * the curve fits to each vertex. Catmull-Rom curves are actually + * a subset of this curve type where the s is set to zero. + *

+ * (This function is not optimized, since it's not expected to + * be called all that often. there are many juicy and obvious + * opimizations in here, but it's probably better to keep the + * code more readable) + */ + protected void curveInit() { + // allocate only if/when used to save startup time + if (curveDrawMatrix == null) { + curveBasisMatrix = new PMatrix3D(); + curveDrawMatrix = new PMatrix3D(); + curveInited = true; + } + + float s = curveTightness; + curveBasisMatrix.set((s-1)/2f, (s+3)/2f, (-3-s)/2f, (1-s)/2f, + (1-s), (-5-s)/2f, (s+2), (s-1)/2f, + (s-1)/2f, 0, (1-s)/2f, 0, + 0, 1, 0, 0); + + //setup_spline_forward(segments, curveForwardMatrix); + splineForward(curveDetail, curveDrawMatrix); + + if (bezierBasisInverse == null) { + bezierBasisInverse = bezierBasisMatrix.get(); + bezierBasisInverse.invert(); + curveToBezierMatrix = new PMatrix3D(); + } + + // TODO only needed for PGraphicsJava2D? if so, move it there + // actually, it's generally useful for other renderers, so keep it + // or hide the implementation elsewhere. + curveToBezierMatrix.set(curveBasisMatrix); + curveToBezierMatrix.preApply(bezierBasisInverse); + + // multiply the basis and forward diff matrices together + // saves much time since this needn't be done for each curve + curveDrawMatrix.apply(curveBasisMatrix); + } + + + /** + * Draws a segment of Catmull-Rom curve. + *

+ * As of 0070, this function no longer doubles the first and + * last points. The curves are a bit more boring, but it's more + * mathematically correct, and properly mirrored in curvePoint(). + *

+ * Identical to typing out:

+   * beginShape();
+   * curveVertex(x1, y1);
+   * curveVertex(x2, y2);
+   * curveVertex(x3, y3);
+   * curveVertex(x4, y4);
+   * endShape();
+   * 
+ */ + public void curve(float x1, float y1, + float x2, float y2, + float x3, float y3, + float x4, float y4) { + beginShape(); + curveVertex(x1, y1); + curveVertex(x2, y2); + curveVertex(x3, y3); + curveVertex(x4, y4); + endShape(); + } + + + public void curve(float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + beginShape(); + curveVertex(x1, y1, z1); + curveVertex(x2, y2, z2); + curveVertex(x3, y3, z3); + curveVertex(x4, y4, z4); + endShape(); + } + + + + ////////////////////////////////////////////////////////////// + + // SPLINE UTILITY FUNCTIONS (used by both Bezier and Catmull-Rom) + + + /** + * Setup forward-differencing matrix to be used for speedy + * curve rendering. It's based on using a specific number + * of curve segments and just doing incremental adds for each + * vertex of the segment, rather than running the mathematically + * expensive cubic equation. + * @param segments number of curve segments to use when drawing + * @param matrix target object for the new matrix + */ + protected void splineForward(int segments, PMatrix3D matrix) { + float f = 1.0f / segments; + float ff = f * f; + float fff = ff * f; + + matrix.set(0, 0, 0, 1, + fff, ff, f, 0, + 6*fff, 2*ff, 0, 0, + 6*fff, 0, 0, 0); + } + + + + ////////////////////////////////////////////////////////////// + + // SMOOTHING + + + /** + * If true in PImage, use bilinear interpolation for copy() + * operations. When inherited by PGraphics, also controls shapes. + */ + public void smooth() { + smooth = true; + } + + + /** + * Disable smoothing. See smooth(). + */ + public void noSmooth() { + smooth = false; + } + + + + ////////////////////////////////////////////////////////////// + + // IMAGE + + + /** + * The mode can only be set to CORNERS, CORNER, and CENTER. + *

+ * Support for CENTER was added in release 0146. + */ + public void imageMode(int mode) { + if ((mode == CORNER) || (mode == CORNERS) || (mode == CENTER)) { + imageMode = mode; + } else { + String msg = + "imageMode() only works with CORNER, CORNERS, or CENTER"; + throw new RuntimeException(msg); + } + } + + + public void image(PImage image, float x, float y) { + // Starting in release 0144, image errors are simply ignored. + // loadImageAsync() sets width and height to -1 when loading fails. + if (image.width == -1 || image.height == -1) return; + + if (imageMode == CORNER || imageMode == CORNERS) { + imageImpl(image, + x, y, x+image.width, y+image.height, + 0, 0, image.width, image.height); + + } else if (imageMode == CENTER) { + float x1 = x - image.width/2; + float y1 = y - image.height/2; + imageImpl(image, + x1, y1, x1+image.width, y1+image.height, + 0, 0, image.width, image.height); + } + } + + + public void image(PImage image, float x, float y, float c, float d) { + image(image, x, y, c, d, 0, 0, image.width, image.height); + } + + + /** + * Draw an image(), also specifying u/v coordinates. + * In this method, the u, v coordinates are always based on image space + * location, regardless of the current textureMode(). + */ + public void image(PImage image, + float a, float b, float c, float d, + int u1, int v1, int u2, int v2) { + // Starting in release 0144, image errors are simply ignored. + // loadImageAsync() sets width and height to -1 when loading fails. + if (image.width == -1 || image.height == -1) return; + + if (imageMode == CORNER) { + if (c < 0) { // reset a negative width + a += c; c = -c; + } + if (d < 0) { // reset a negative height + b += d; d = -d; + } + + imageImpl(image, + a, b, a + c, b + d, + u1, v1, u2, v2); + + } else if (imageMode == CORNERS) { + if (c < a) { // reverse because x2 < x1 + float temp = a; a = c; c = temp; + } + if (d < b) { // reverse because y2 < y1 + float temp = b; b = d; d = temp; + } + + imageImpl(image, + a, b, c, d, + u1, v1, u2, v2); + + } else if (imageMode == CENTER) { + // c and d are width/height + if (c < 0) c = -c; + if (d < 0) d = -d; + float x1 = a - c/2; + float y1 = b - d/2; + + imageImpl(image, + x1, y1, x1 + c, y1 + d, + u1, v1, u2, v2); + } + } + + + /** + * Expects x1, y1, x2, y2 coordinates where (x2 >= x1) and (y2 >= y1). + * If tint() has been called, the image will be colored. + *

+ * The default implementation draws an image as a textured quad. + * The (u, v) coordinates are in image space (they're ints, after all..) + */ + protected void imageImpl(PImage image, + float x1, float y1, float x2, float y2, + int u1, int v1, int u2, int v2) { + boolean savedStroke = stroke; +// boolean savedFill = fill; + int savedTextureMode = textureMode; + + stroke = false; +// fill = true; + textureMode = IMAGE; + +// float savedFillR = fillR; +// float savedFillG = fillG; +// float savedFillB = fillB; +// float savedFillA = fillA; +// +// if (tint) { +// fillR = tintR; +// fillG = tintG; +// fillB = tintB; +// fillA = tintA; +// +// } else { +// fillR = 1; +// fillG = 1; +// fillB = 1; +// fillA = 1; +// } + + beginShape(QUADS); + texture(image); + vertex(x1, y1, u1, v1); + vertex(x1, y2, u1, v2); + vertex(x2, y2, u2, v2); + vertex(x2, y1, u2, v1); + endShape(); + + stroke = savedStroke; +// fill = savedFill; + textureMode = savedTextureMode; + +// fillR = savedFillR; +// fillG = savedFillG; +// fillB = savedFillB; +// fillA = savedFillA; + } + + + + ////////////////////////////////////////////////////////////// + + // SHAPE + + + /** + * Set the orientation for the shape() command (like imageMode() or rectMode()). + * @param mode Either CORNER, CORNERS, or CENTER. + */ + public void shapeMode(int mode) { + this.shapeMode = mode; + } + + + public void shape(PShape shape) { + if (shape.isVisible()) { // don't do expensive matrix ops if invisible + if (shapeMode == CENTER) { + pushMatrix(); + translate(-shape.getWidth()/2, -shape.getHeight()/2); + } + + shape.draw(this); // needs to handle recorder too + + if (shapeMode == CENTER) { + popMatrix(); + } + } + } + + + /** + * Convenience method to draw at a particular location. + */ + public void shape(PShape shape, float x, float y) { + if (shape.isVisible()) { // don't do expensive matrix ops if invisible + pushMatrix(); + + if (shapeMode == CENTER) { + translate(x - shape.getWidth()/2, y - shape.getHeight()/2); + + } else if ((shapeMode == CORNER) || (shapeMode == CORNERS)) { + translate(x, y); + } + shape.draw(this); + + popMatrix(); + } + } + + + public void shape(PShape shape, float x, float y, float c, float d) { + if (shape.isVisible()) { // don't do expensive matrix ops if invisible + pushMatrix(); + + if (shapeMode == CENTER) { + // x and y are center, c and d refer to a diameter + translate(x - c/2f, y - d/2f); + scale(c / shape.getWidth(), d / shape.getHeight()); + + } else if (shapeMode == CORNER) { + translate(x, y); + scale(c / shape.getWidth(), d / shape.getHeight()); + + } else if (shapeMode == CORNERS) { + // c and d are x2/y2, make them into width/height + c -= x; + d -= y; + // then same as above + translate(x, y); + scale(c / shape.getWidth(), d / shape.getHeight()); + } + shape.draw(this); + + popMatrix(); + } + } + + + + ////////////////////////////////////////////////////////////// + + // TEXT/FONTS + + + /** + * Sets the alignment of the text to one of LEFT, CENTER, or RIGHT. + * This will also reset the vertical text alignment to BASELINE. + */ + public void textAlign(int align) { + textAlign(align, BASELINE); + } + + + /** + * Sets the horizontal and vertical alignment of the text. The horizontal + * alignment can be one of LEFT, CENTER, or RIGHT. The vertical alignment + * can be TOP, BOTTOM, CENTER, or the BASELINE (the default). + */ + public void textAlign(int alignX, int alignY) { + textAlign = alignX; + textAlignY = alignY; + } + + + /** + * Returns the ascent of the current font at the current size. + * This is a method, rather than a variable inside the PGraphics object + * because it requires calculation. + */ + public float textAscent() { + if (textFont == null) { + showTextFontException("textAscent"); + } + return textFont.ascent() * ((textMode == SCREEN) ? textFont.size : textSize); + } + + + /** + * Returns the descent of the current font at the current size. + * This is a method, rather than a variable inside the PGraphics object + * because it requires calculation. + */ + public float textDescent() { + if (textFont == null) { + showTextFontException("textDescent"); + } + return textFont.descent() * ((textMode == SCREEN) ? textFont.size : textSize); + } + + + /** + * Sets the current font. The font's size will be the "natural" + * size of this font (the size that was set when using "Create Font"). + * The leading will also be reset. + */ + public void textFont(PFont which) { + if (which != null) { + textFont = which; + if (hints[ENABLE_NATIVE_FONTS]) { + //if (which.font == null) { + which.findFont(); + //} + } + /* + textFontNative = which.font; + + //textFontNativeMetrics = null; + // changed for rev 0104 for textMode(SHAPE) in opengl + if (textFontNative != null) { + // TODO need a better way to handle this. could use reflection to get + // rid of the warning, but that'd be a little silly. supporting this is + // an artifact of supporting java 1.1, otherwise we'd use getLineMetrics, + // as recommended by the @deprecated flag. + textFontNativeMetrics = + Toolkit.getDefaultToolkit().getFontMetrics(textFontNative); + // The following is what needs to be done, however we need to be able + // to get the actual graphics context where the drawing is happening. + // For instance, parent.getGraphics() doesn't work for OpenGL since + // an OpenGL drawing surface is an embedded component. +// if (parent != null) { +// textFontNativeMetrics = parent.getGraphics().getFontMetrics(textFontNative); +// } + + // float w = font.getStringBounds(text, g2.getFontRenderContext()).getWidth(); + } + */ + textSize(which.size); + + } else { + throw new RuntimeException(ERROR_TEXTFONT_NULL_PFONT); + } + } + + + /** + * Useful function to set the font and size at the same time. + */ + public void textFont(PFont which, float size) { + textFont(which); + textSize(size); + } + + + /** + * Set the text leading to a specific value. If using a custom + * value for the text leading, you'll have to call textLeading() + * again after any calls to textSize(). + */ + public void textLeading(float leading) { + textLeading = leading; + } + + + /** + * Sets the text rendering/placement to be either SCREEN (direct + * to the screen, exact coordinates, only use the font's original size) + * or MODEL (the default, where text is manipulated by translate() and + * can have a textSize). The text size cannot be set when using + * textMode(SCREEN), because it uses the pixels directly from the font. + */ + public void textMode(int mode) { + // CENTER and MODEL overlap (they're both 3) + if ((mode == LEFT) || (mode == RIGHT)) { + showWarning("Since Processing beta, textMode() is now textAlign()."); + return; + } +// if ((mode != SCREEN) && (mode != MODEL)) { +// showError("Only textMode(SCREEN) and textMode(MODEL) " + +// "are available with this renderer."); +// } + + if (textModeCheck(mode)) { + textMode = mode; + } else { + String modeStr = String.valueOf(mode); + switch (mode) { + case SCREEN: modeStr = "SCREEN"; break; + case MODEL: modeStr = "MODEL"; break; + case SHAPE: modeStr = "SHAPE"; break; + } + showWarning("textMode(" + modeStr + ") is not supported by this renderer."); + } + + // reset the font to its natural size + // (helps with width calculations and all that) + //if (textMode == SCREEN) { + //textSize(textFont.size); + //} + + //} else { + //throw new RuntimeException("use textFont() before textMode()"); + //} + } + + + protected boolean textModeCheck(int mode) { + return true; + } + + + /** + * Sets the text size, also resets the value for the leading. + */ + public void textSize(float size) { + if (textFont != null) { +// if ((textMode == SCREEN) && (size != textFont.size)) { +// throw new RuntimeException("textSize() is ignored with " + +// "textMode(SCREEN)"); +// } + textSize = size; + textLeading = (textAscent() + textDescent()) * 1.275f; + + } else { + showTextFontException("textSize"); + } + } + + + // ........................................................ + + + public float textWidth(char c) { + textWidthBuffer[0] = c; + return textWidthImpl(textWidthBuffer, 0, 1); + } + + + /** + * Return the width of a line of text. If the text has multiple + * lines, this returns the length of the longest line. + */ + public float textWidth(String str) { + if (textFont == null) { + showTextFontException("textWidth"); + } + + int length = str.length(); + if (length > textWidthBuffer.length) { + textWidthBuffer = new char[length + 10]; + } + str.getChars(0, length, textWidthBuffer, 0); + + float wide = 0; + int index = 0; + int start = 0; + + while (index < length) { + if (textWidthBuffer[index] == '\n') { + wide = Math.max(wide, textWidthImpl(textWidthBuffer, start, index)); + start = index+1; + } + index++; + } + if (start < length) { + wide = Math.max(wide, textWidthImpl(textWidthBuffer, start, index)); + } + return wide; + } + + + /** + * Implementation of returning the text width of + * the chars [start, stop) in the buffer. + * Unlike the previous version that was inside PFont, this will + * return the size not of a 1 pixel font, but the actual current size. + */ + protected float textWidthImpl(char buffer[], int start, int stop) { + float wide = 0; + for (int i = start; i < stop; i++) { + // could add kerning here, but it just ain't implemented + wide += textFont.width(buffer[i]) * textSize; + } + return wide; + } + + + // ........................................................ + + + /** + * Write text where we just left off. + */ + public void text(char c) { + text(c, textX, textY, textZ); + } + + + /** + * Draw a single character on screen. + * Extremely slow when used with textMode(SCREEN) and Java 2D, + * because loadPixels has to be called first and updatePixels last. + */ + public void text(char c, float x, float y) { + if (textFont == null) { + showTextFontException("text"); + } + + if (textMode == SCREEN) loadPixels(); + + if (textAlignY == CENTER) { + y += textAscent() / 2; + } else if (textAlignY == TOP) { + y += textAscent(); + } else if (textAlignY == BOTTOM) { + y -= textDescent(); + //} else if (textAlignY == BASELINE) { + // do nothing + } + + textBuffer[0] = c; + textLineAlignImpl(textBuffer, 0, 1, x, y); + + if (textMode == SCREEN) updatePixels(); + } + + + /** + * Draw a single character on screen (with a z coordinate) + */ + public void text(char c, float x, float y, float z) { +// if ((z != 0) && (textMode == SCREEN)) { +// String msg = "textMode(SCREEN) cannot have a z coordinate"; +// throw new RuntimeException(msg); +// } + + if (z != 0) translate(0, 0, z); // slowness, badness + + text(c, x, y); + textZ = z; + + if (z != 0) translate(0, 0, -z); + } + + + /** + * Write text where we just left off. + */ + public void text(String str) { + text(str, textX, textY, textZ); + } + + + /** + * Draw a chunk of text. + * Newlines that are \n (Unix newline or linefeed char, ascii 10) + * are honored, but \r (carriage return, Windows and Mac OS) are + * ignored. + */ + public void text(String str, float x, float y) { + if (textFont == null) { + showTextFontException("text"); + } + + if (textMode == SCREEN) loadPixels(); + + int length = str.length(); + if (length > textBuffer.length) { + textBuffer = new char[length + 10]; + } + str.getChars(0, length, textBuffer, 0); + text(textBuffer, 0, length, x, y); + } + + + /** + * Method to draw text from an array of chars. This method will usually be + * more efficient than drawing from a String object, because the String will + * not be converted to a char array before drawing. + */ + public void text(char[] chars, int start, int stop, float x, float y) { + // If multiple lines, sum the height of the additional lines + float high = 0; //-textAscent(); + for (int i = start; i < stop; i++) { + if (chars[i] == '\n') { + high += textLeading; + } + } + if (textAlignY == CENTER) { + // for a single line, this adds half the textAscent to y + // for multiple lines, subtract half the additional height + //y += (textAscent() - textDescent() - high)/2; + y += (textAscent() - high)/2; + } else if (textAlignY == TOP) { + // for a single line, need to add textAscent to y + // for multiple lines, no different + y += textAscent(); + } else if (textAlignY == BOTTOM) { + // for a single line, this is just offset by the descent + // for multiple lines, subtract leading for each line + y -= textDescent() + high; + //} else if (textAlignY == BASELINE) { + // do nothing + } + +// int start = 0; + int index = 0; + while (index < stop) { //length) { + if (chars[index] == '\n') { + textLineAlignImpl(chars, start, index, x, y); + start = index + 1; + y += textLeading; + } + index++; + } + if (start < stop) { //length) { + textLineAlignImpl(chars, start, index, x, y); + } + if (textMode == SCREEN) updatePixels(); + } + + + /** + * Same as above but with a z coordinate. + */ + public void text(String str, float x, float y, float z) { + if (z != 0) translate(0, 0, z); // slow! + + text(str, x, y); + textZ = z; + + if (z != 0) translate(0, 0, -z); // inaccurate! + } + + + public void text(char[] chars, int start, int stop, + float x, float y, float z) { + if (z != 0) translate(0, 0, z); // slow! + + text(chars, start, stop, x, y); + textZ = z; + + if (z != 0) translate(0, 0, -z); // inaccurate! + } + + + /** + * Draw text in a box that is constrained to a particular size. + * The current rectMode() determines what the coordinates mean + * (whether x1/y1/x2/y2 or x/y/w/h). + *

+ * Note that the x,y coords of the start of the box + * will align with the *ascent* of the text, not the baseline, + * as is the case for the other text() functions. + *

+ * Newlines that are \n (Unix newline or linefeed char, ascii 10) + * are honored, and \r (carriage return, Windows and Mac OS) are + * ignored. + */ + public void text(String str, float x1, float y1, float x2, float y2) { + if (textFont == null) { + showTextFontException("text"); + } + + if (textMode == SCREEN) loadPixels(); + + float hradius, vradius; + switch (rectMode) { + case CORNER: + x2 += x1; y2 += y1; + break; + case RADIUS: + hradius = x2; + vradius = y2; + x2 = x1 + hradius; + y2 = y1 + vradius; + x1 -= hradius; + y1 -= vradius; + break; + case CENTER: + hradius = x2 / 2.0f; + vradius = y2 / 2.0f; + x2 = x1 + hradius; + y2 = y1 + vradius; + x1 -= hradius; + y1 -= vradius; + } + if (x2 < x1) { + float temp = x1; x1 = x2; x2 = temp; + } + if (y2 < y1) { + float temp = y1; y1 = y2; y2 = temp; + } + +// float currentY = y1; + float boxWidth = x2 - x1; + +// // ala illustrator, the text itself must fit inside the box +// currentY += textAscent(); //ascent() * textSize; +// // if the box is already too small, tell em to f off +// if (currentY > y2) return; + + float spaceWidth = textWidth(' '); + + if (textBreakStart == null) { + textBreakStart = new int[20]; + textBreakStop = new int[20]; + } + textBreakCount = 0; + + int length = str.length(); + if (length + 1 > textBuffer.length) { + textBuffer = new char[length + 1]; + } + str.getChars(0, length, textBuffer, 0); + // add a fake newline to simplify calculations + textBuffer[length++] = '\n'; + + int sentenceStart = 0; + for (int i = 0; i < length; i++) { + if (textBuffer[i] == '\n') { +// currentY = textSentence(textBuffer, sentenceStart, i, +// lineX, boxWidth, currentY, y2, spaceWidth); + boolean legit = + textSentence(textBuffer, sentenceStart, i, boxWidth, spaceWidth); + if (!legit) break; +// if (Float.isNaN(currentY)) break; // word too big (or error) +// if (currentY > y2) break; // past the box + sentenceStart = i + 1; + } + } + + // lineX is the position where the text starts, which is adjusted + // to left/center/right based on the current textAlign + float lineX = x1; //boxX1; + if (textAlign == CENTER) { + lineX = lineX + boxWidth/2f; + } else if (textAlign == RIGHT) { + lineX = x2; //boxX2; + } + + float boxHeight = y2 - y1; + //int lineFitCount = 1 + PApplet.floor((boxHeight - textAscent()) / textLeading); + // incorporate textAscent() for the top (baseline will be y1 + ascent) + // and textDescent() for the bottom, so that lower parts of letters aren't + // outside the box. [0151] + float topAndBottom = textAscent() + textDescent(); + int lineFitCount = 1 + PApplet.floor((boxHeight - topAndBottom) / textLeading); + int lineCount = Math.min(textBreakCount, lineFitCount); + + if (textAlignY == CENTER) { + float lineHigh = textAscent() + textLeading * (lineCount - 1); + float y = y1 + textAscent() + (boxHeight - lineHigh) / 2; + for (int i = 0; i < lineCount; i++) { + textLineAlignImpl(textBuffer, textBreakStart[i], textBreakStop[i], lineX, y); + y += textLeading; + } + + } else if (textAlignY == BOTTOM) { + float y = y2 - textDescent() - textLeading * (lineCount - 1); + for (int i = 0; i < lineCount; i++) { + textLineAlignImpl(textBuffer, textBreakStart[i], textBreakStop[i], lineX, y); + y += textLeading; + } + + } else { // TOP or BASELINE just go to the default + float y = y1 + textAscent(); + for (int i = 0; i < lineCount; i++) { + textLineAlignImpl(textBuffer, textBreakStart[i], textBreakStop[i], lineX, y); + y += textLeading; + } + } + + if (textMode == SCREEN) updatePixels(); + } + + + /** + * Emit a sentence of text, defined as a chunk of text without any newlines. + * @param stop non-inclusive, the end of the text in question + */ + protected boolean textSentence(char[] buffer, int start, int stop, + float boxWidth, float spaceWidth) { + float runningX = 0; + + // Keep track of this separately from index, since we'll need to back up + // from index when breaking words that are too long to fit. + int lineStart = start; + int wordStart = start; + int index = start; + while (index <= stop) { + // boundary of a word or end of this sentence + if ((buffer[index] == ' ') || (index == stop)) { + float wordWidth = textWidthImpl(buffer, wordStart, index); + + if (runningX + wordWidth > boxWidth) { + if (runningX != 0) { + // Next word is too big, output the current line and advance + index = wordStart; + textSentenceBreak(lineStart, index); + // Eat whitespace because multiple spaces don't count for s* + // when they're at the end of a line. + while ((index < stop) && (buffer[index] == ' ')) { + index++; + } + } else { // (runningX == 0) + // If this is the first word on the line, and its width is greater + // than the width of the text box, then break the word where at the + // max width, and send the rest of the word to the next line. + do { + index--; + if (index == wordStart) { + // Not a single char will fit on this line. screw 'em. + //System.out.println("screw you"); + return false; //Float.NaN; + } + wordWidth = textWidthImpl(buffer, wordStart, index); + } while (wordWidth > boxWidth); + + //textLineImpl(buffer, lineStart, index, x, y); + textSentenceBreak(lineStart, index); + } + lineStart = index; + wordStart = index; + runningX = 0; + + } else if (index == stop) { + // last line in the block, time to unload + //textLineImpl(buffer, lineStart, index, x, y); + textSentenceBreak(lineStart, index); +// y += textLeading; + index++; + + } else { // this word will fit, just add it to the line + runningX += wordWidth + spaceWidth; + wordStart = index + 1; // move on to the next word + index++; + } + } else { // not a space or the last character + index++; // this is just another letter + } + } +// return y; + return true; + } + + + protected void textSentenceBreak(int start, int stop) { + if (textBreakCount == textBreakStart.length) { + textBreakStart = PApplet.expand(textBreakStart); + textBreakStop = PApplet.expand(textBreakStop); + } + textBreakStart[textBreakCount] = start; + textBreakStop[textBreakCount] = stop; + textBreakCount++; + } + + + public void text(String s, float x1, float y1, float x2, float y2, float z) { + if (z != 0) translate(0, 0, z); // slowness, badness + + text(s, x1, y1, x2, y2); + textZ = z; + + if (z != 0) translate(0, 0, -z); // TEMPORARY HACK! SLOW! + } + + + public void text(int num, float x, float y) { + text(String.valueOf(num), x, y); + } + + + public void text(int num, float x, float y, float z) { + text(String.valueOf(num), x, y, z); + } + + + /** + * This does a basic number formatting, to avoid the + * generally ugly appearance of printing floats. + * Users who want more control should use their own nf() cmmand, + * or if they want the long, ugly version of float, + * use String.valueOf() to convert the float to a String first. + */ + public void text(float num, float x, float y) { + text(PApplet.nfs(num, 0, 3), x, y); + } + + + public void text(float num, float x, float y, float z) { + text(PApplet.nfs(num, 0, 3), x, y, z); + } + + + + ////////////////////////////////////////////////////////////// + + // TEXT IMPL + + // These are most likely to be overridden by subclasses, since the other + // (public) functions handle generic features like setting alignment. + + + /** + * Handles placement of a text line, then calls textLineImpl + * to actually render at the specific point. + */ + protected void textLineAlignImpl(char buffer[], int start, int stop, + float x, float y) { + if (textAlign == CENTER) { + x -= textWidthImpl(buffer, start, stop) / 2f; + + } else if (textAlign == RIGHT) { + x -= textWidthImpl(buffer, start, stop); + } + + textLineImpl(buffer, start, stop, x, y); + } + + + /** + * Implementation of actual drawing for a line of text. + */ + protected void textLineImpl(char buffer[], int start, int stop, + float x, float y) { + for (int index = start; index < stop; index++) { + textCharImpl(buffer[index], x, y); + + // this doesn't account for kerning + x += textWidth(buffer[index]); + } + textX = x; + textY = y; + textZ = 0; // this will get set by the caller if non-zero + } + + + protected void textCharImpl(char ch, float x, float y) { //, float z) { + int index = textFont.index(ch); + if (index == -1) return; + + PImage glyph = textFont.images[index]; + + if (textMode == MODEL) { + float high = (float) textFont.height[index] / textFont.fheight; + float bwidth = (float) textFont.width[index] / textFont.fwidth; + float lextent = (float) textFont.leftExtent[index] / textFont.fwidth; + float textent = (float) textFont.topExtent[index] / textFont.fheight; + + float x1 = x + lextent * textSize; + float y1 = y - textent * textSize; + float x2 = x1 + bwidth * textSize; + float y2 = y1 + high * textSize; + + textCharModelImpl(glyph, + x1, y1, x2, y2, + //x1, y1, z, x2, y2, z, + textFont.width[index], textFont.height[index]); + + } else if (textMode == SCREEN) { + int xx = (int) x + textFont.leftExtent[index];; + int yy = (int) y - textFont.topExtent[index]; + + int w0 = textFont.width[index]; + int h0 = textFont.height[index]; + + textCharScreenImpl(glyph, xx, yy, w0, h0); + } + } + + + protected void textCharModelImpl(PImage glyph, + float x1, float y1, //float z1, + float x2, float y2, //float z2, + int u2, int v2) { + boolean savedTint = tint; + int savedTintColor = tintColor; + float savedTintR = tintR; + float savedTintG = tintG; + float savedTintB = tintB; + float savedTintA = tintA; + boolean savedTintAlpha = tintAlpha; + + tint = true; + tintColor = fillColor; + tintR = fillR; + tintG = fillG; + tintB = fillB; + tintA = fillA; + tintAlpha = fillAlpha; + + imageImpl(glyph, x1, y1, x2, y2, 0, 0, u2, v2); + + tint = savedTint; + tintColor = savedTintColor; + tintR = savedTintR; + tintG = savedTintG; + tintB = savedTintB; + tintA = savedTintA; + tintAlpha = savedTintAlpha; + } + + + protected void textCharScreenImpl(PImage glyph, + int xx, int yy, + int w0, int h0) { + int x0 = 0; + int y0 = 0; + + if ((xx >= width) || (yy >= height) || + (xx + w0 < 0) || (yy + h0 < 0)) return; + + if (xx < 0) { + x0 -= xx; + w0 += xx; + xx = 0; + } + if (yy < 0) { + y0 -= yy; + h0 += yy; + yy = 0; + } + if (xx + w0 > width) { + w0 -= ((xx + w0) - width); + } + if (yy + h0 > height) { + h0 -= ((yy + h0) - height); + } + + int fr = fillRi; + int fg = fillGi; + int fb = fillBi; + int fa = fillAi; + + int pixels1[] = glyph.pixels; //images[glyph].pixels; + + // TODO this can be optimized a bit + for (int row = y0; row < y0 + h0; row++) { + for (int col = x0; col < x0 + w0; col++) { + int a1 = (fa * pixels1[row * textFont.twidth + col]) >> 8; + int a2 = a1 ^ 0xff; + //int p1 = pixels1[row * glyph.width + col]; + int p2 = pixels[(yy + row-y0)*width + (xx+col-x0)]; + + pixels[(yy + row-y0)*width + xx+col-x0] = + (0xff000000 | + (((a1 * fr + a2 * ((p2 >> 16) & 0xff)) & 0xff00) << 8) | + (( a1 * fg + a2 * ((p2 >> 8) & 0xff)) & 0xff00) | + (( a1 * fb + a2 * ( p2 & 0xff)) >> 8)); + } + } + } + + + + ////////////////////////////////////////////////////////////// + + // MATRIX STACK + + + /** + * Push a copy of the current transformation matrix onto the stack. + */ + public void pushMatrix() { + showMethodWarning("pushMatrix"); + } + + + /** + * Replace the current transformation matrix with the top of the stack. + */ + public void popMatrix() { + showMethodWarning("popMatrix"); + } + + + + ////////////////////////////////////////////////////////////// + + // MATRIX TRANSFORMATIONS + + + /** + * Translate in X and Y. + */ + public void translate(float tx, float ty) { + showMissingWarning("translate"); + } + + + /** + * Translate in X, Y, and Z. + */ + public void translate(float tx, float ty, float tz) { + showMissingWarning("translate"); + } + + + /** + * Two dimensional rotation. + * + * Same as rotateZ (this is identical to a 3D rotation along the z-axis) + * but included for clarity. It'd be weird for people drawing 2D graphics + * to be using rotateZ. And they might kick our a-- for the confusion. + * + * Additional background. + */ + public void rotate(float angle) { + showMissingWarning("rotate"); + } + + + /** + * Rotate around the X axis. + */ + public void rotateX(float angle) { + showMethodWarning("rotateX"); + } + + + /** + * Rotate around the Y axis. + */ + public void rotateY(float angle) { + showMethodWarning("rotateY"); + } + + + /** + * Rotate around the Z axis. + * + * The functions rotate() and rotateZ() are identical, it's just that it make + * sense to have rotate() and then rotateX() and rotateY() when using 3D; + * nor does it make sense to use a function called rotateZ() if you're only + * doing things in 2D. so we just decided to have them both be the same. + */ + public void rotateZ(float angle) { + showMethodWarning("rotateZ"); + } + + + /** + * Rotate about a vector in space. Same as the glRotatef() function. + */ + public void rotate(float angle, float vx, float vy, float vz) { + showMissingWarning("rotate"); + } + + + /** + * Scale in all dimensions. + */ + public void scale(float s) { + showMissingWarning("scale"); + } + + + /** + * Scale in X and Y. Equivalent to scale(sx, sy, 1). + * + * Not recommended for use in 3D, because the z-dimension is just + * scaled by 1, since there's no way to know what else to scale it by. + */ + public void scale(float sx, float sy) { + showMissingWarning("scale"); + } + + + /** + * Scale in X, Y, and Z. + */ + public void scale(float x, float y, float z) { + showMissingWarning("scale"); + } + + + ////////////////////////////////////////////////////////////// + + // MATRIX FULL MONTY + + + /** + * Set the current transformation matrix to identity. + */ + public void resetMatrix() { + showMethodWarning("resetMatrix"); + } + + + public void applyMatrix(PMatrix source) { + if (source instanceof PMatrix2D) { + applyMatrix((PMatrix2D) source); + } else if (source instanceof PMatrix3D) { + applyMatrix((PMatrix3D) source); + } + } + + + public void applyMatrix(PMatrix2D source) { + applyMatrix(source.m00, source.m01, source.m02, + source.m10, source.m11, source.m12); + } + + + /** + * Apply a 3x2 affine transformation matrix. + */ + public void applyMatrix(float n00, float n01, float n02, + float n10, float n11, float n12) { + showMissingWarning("applyMatrix"); + } + + + public void applyMatrix(PMatrix3D source) { + applyMatrix(source.m00, source.m01, source.m02, source.m03, + source.m10, source.m11, source.m12, source.m13, + source.m20, source.m21, source.m22, source.m23, + source.m30, source.m31, source.m32, source.m33); + } + + + /** + * Apply a 4x4 transformation matrix. + */ + public void applyMatrix(float n00, float n01, float n02, float n03, + float n10, float n11, float n12, float n13, + float n20, float n21, float n22, float n23, + float n30, float n31, float n32, float n33) { + showMissingWarning("applyMatrix"); + } + + + + ////////////////////////////////////////////////////////////// + + // MATRIX GET/SET/PRINT + + + public PMatrix getMatrix() { + showMissingWarning("getMatrix"); + return null; + } + + + /** + * Copy the current transformation matrix into the specified target. + * Pass in null to create a new matrix. + */ + public PMatrix2D getMatrix(PMatrix2D target) { + showMissingWarning("getMatrix"); + return null; + } + + + /** + * Copy the current transformation matrix into the specified target. + * Pass in null to create a new matrix. + */ + public PMatrix3D getMatrix(PMatrix3D target) { + showMissingWarning("getMatrix"); + return null; + } + + + /** + * Set the current transformation matrix to the contents of another. + */ + public void setMatrix(PMatrix source) { + if (source instanceof PMatrix2D) { + setMatrix((PMatrix2D) source); + } else if (source instanceof PMatrix3D) { + setMatrix((PMatrix3D) source); + } + } + + + /** + * Set the current transformation to the contents of the specified source. + */ + public void setMatrix(PMatrix2D source) { + showMissingWarning("setMatrix"); + } + + + /** + * Set the current transformation to the contents of the specified source. + */ + public void setMatrix(PMatrix3D source) { + showMissingWarning("setMatrix"); + } + + + /** + * Print the current model (or "transformation") matrix. + */ + public void printMatrix() { + showMethodWarning("printMatrix"); + } + + + + ////////////////////////////////////////////////////////////// + + // CAMERA + + + public void beginCamera() { + showMethodWarning("beginCamera"); + } + + + public void endCamera() { + showMethodWarning("endCamera"); + } + + + public void camera() { + showMissingWarning("camera"); + } + + + public void camera(float eyeX, float eyeY, float eyeZ, + float centerX, float centerY, float centerZ, + float upX, float upY, float upZ) { + showMissingWarning("camera"); + } + + + public void printCamera() { + showMethodWarning("printCamera"); + } + + + + ////////////////////////////////////////////////////////////// + + // PROJECTION + + + public void ortho() { + showMissingWarning("ortho"); + } + + + public void ortho(float left, float right, + float bottom, float top, + float near, float far) { + showMissingWarning("ortho"); + } + + + public void perspective() { + showMissingWarning("perspective"); + } + + + public void perspective(float fovy, float aspect, float zNear, float zFar) { + showMissingWarning("perspective"); + } + + + public void frustum(float left, float right, + float bottom, float top, + float near, float far) { + showMethodWarning("frustum"); + } + + + public void printProjection() { + showMethodWarning("printCamera"); + } + + + + ////////////////////////////////////////////////////////////// + + // SCREEN TRANSFORMS + + + /** + * Given an x and y coordinate, returns the x position of where + * that point would be placed on screen, once affected by translate(), + * scale(), or any other transformations. + */ + public float screenX(float x, float y) { + showMissingWarning("screenX"); + return 0; + } + + + /** + * Given an x and y coordinate, returns the y position of where + * that point would be placed on screen, once affected by translate(), + * scale(), or any other transformations. + */ + public float screenY(float x, float y) { + showMissingWarning("screenY"); + return 0; + } + + + /** + * Maps a three dimensional point to its placement on-screen. + *

+ * Given an (x, y, z) coordinate, returns the x position of where + * that point would be placed on screen, once affected by translate(), + * scale(), or any other transformations. + */ + public float screenX(float x, float y, float z) { + showMissingWarning("screenX"); + return 0; + } + + + /** + * Maps a three dimensional point to its placement on-screen. + *

+ * Given an (x, y, z) coordinate, returns the y position of where + * that point would be placed on screen, once affected by translate(), + * scale(), or any other transformations. + */ + public float screenY(float x, float y, float z) { + showMissingWarning("screenY"); + return 0; + } + + + /** + * Maps a three dimensional point to its placement on-screen. + *

+ * Given an (x, y, z) coordinate, returns its z value. + * This value can be used to determine if an (x, y, z) coordinate + * is in front or in back of another (x, y, z) coordinate. + * The units are based on how the zbuffer is set up, and don't + * relate to anything "real". They're only useful for in + * comparison to another value obtained from screenZ(), + * or directly out of the zbuffer[]. + */ + public float screenZ(float x, float y, float z) { + showMissingWarning("screenZ"); + return 0; + } + + + /** + * Returns the model space x value for an x, y, z coordinate. + *

+ * This will give you a coordinate after it has been transformed + * by translate(), rotate(), and camera(), but not yet transformed + * by the projection matrix. For instance, his can be useful for + * figuring out how points in 3D space relate to the edge + * coordinates of a shape. + */ + public float modelX(float x, float y, float z) { + showMissingWarning("modelX"); + return 0; + } + + + /** + * Returns the model space y value for an x, y, z coordinate. + */ + public float modelY(float x, float y, float z) { + showMissingWarning("modelY"); + return 0; + } + + + /** + * Returns the model space z value for an x, y, z coordinate. + */ + public float modelZ(float x, float y, float z) { + showMissingWarning("modelZ"); + return 0; + } + + + + ////////////////////////////////////////////////////////////// + + // STYLE + + + public void pushStyle() { + if (styleStackDepth == styleStack.length) { + styleStack = (PStyle[]) PApplet.expand(styleStack); + } + if (styleStack[styleStackDepth] == null) { + styleStack[styleStackDepth] = new PStyle(); + } + PStyle s = styleStack[styleStackDepth++]; + getStyle(s); + } + + + public void popStyle() { + if (styleStackDepth == 0) { + throw new RuntimeException("Too many popStyle() without enough pushStyle()"); + } + styleStackDepth--; + style(styleStack[styleStackDepth]); + } + + + public void style(PStyle s) { + // if (s.smooth) { + // smooth(); + // } else { + // noSmooth(); + // } + + imageMode(s.imageMode); + rectMode(s.rectMode); + ellipseMode(s.ellipseMode); + shapeMode(s.shapeMode); + + if (s.tint) { + tint(s.tintColor); + } else { + noTint(); + } + if (s.fill) { + fill(s.fillColor); + } else { + noFill(); + } + if (s.stroke) { + stroke(s.strokeColor); + } else { + noStroke(); + } + strokeWeight(s.strokeWeight); + strokeCap(s.strokeCap); + strokeJoin(s.strokeJoin); + + // Set the colorMode() for the material properties. + // TODO this is really inefficient, need to just have a material() method, + // but this has the least impact to the API. + colorMode(RGB, 1); + ambient(s.ambientR, s.ambientG, s.ambientB); + emissive(s.emissiveR, s.emissiveG, s.emissiveB); + specular(s.specularR, s.specularG, s.specularB); + shininess(s.shininess); + + /* + s.ambientR = ambientR; + s.ambientG = ambientG; + s.ambientB = ambientB; + s.specularR = specularR; + s.specularG = specularG; + s.specularB = specularB; + s.emissiveR = emissiveR; + s.emissiveG = emissiveG; + s.emissiveB = emissiveB; + s.shininess = shininess; + */ + // material(s.ambientR, s.ambientG, s.ambientB, + // s.emissiveR, s.emissiveG, s.emissiveB, + // s.specularR, s.specularG, s.specularB, + // s.shininess); + + // Set this after the material properties. + colorMode(s.colorMode, + s.colorModeX, s.colorModeY, s.colorModeZ, s.colorModeA); + + // This is a bit asymmetric, since there's no way to do "noFont()", + // and a null textFont will produce an error (since usually that means that + // the font couldn't load properly). So in some cases, the font won't be + // 'cleared' to null, even though that's technically correct. + if (s.textFont != null) { + textFont(s.textFont, s.textSize); + textLeading(s.textLeading); + } + // These don't require a font to be set. + textAlign(s.textAlign, s.textAlignY); + textMode(s.textMode); + } + + + public PStyle getStyle() { // ignore + return getStyle(null); + } + + + public PStyle getStyle(PStyle s) { // ignore + if (s == null) { + s = new PStyle(); + } + + s.imageMode = imageMode; + s.rectMode = rectMode; + s.ellipseMode = ellipseMode; + s.shapeMode = shapeMode; + + s.colorMode = colorMode; + s.colorModeX = colorModeX; + s.colorModeY = colorModeY; + s.colorModeZ = colorModeZ; + s.colorModeA = colorModeA; + + s.tint = tint; + s.tintColor = tintColor; + s.fill = fill; + s.fillColor = fillColor; + s.stroke = stroke; + s.strokeColor = strokeColor; + s.strokeWeight = strokeWeight; + s.strokeCap = strokeCap; + s.strokeJoin = strokeJoin; + + s.ambientR = ambientR; + s.ambientG = ambientG; + s.ambientB = ambientB; + s.specularR = specularR; + s.specularG = specularG; + s.specularB = specularB; + s.emissiveR = emissiveR; + s.emissiveG = emissiveG; + s.emissiveB = emissiveB; + s.shininess = shininess; + + s.textFont = textFont; + s.textAlign = textAlign; + s.textAlignY = textAlignY; + s.textMode = textMode; + s.textSize = textSize; + s.textLeading = textLeading; + + return s; + } + + + + ////////////////////////////////////////////////////////////// + + // STROKE CAP/JOIN/WEIGHT + + + public void strokeWeight(float weight) { + strokeWeight = weight; + } + + + public void strokeJoin(int join) { + strokeJoin = join; + } + + + public void strokeCap(int cap) { + strokeCap = cap; + } + + + + ////////////////////////////////////////////////////////////// + + // STROKE COLOR + + + public void noStroke() { + stroke = false; + } + + + /** + * Set the tint to either a grayscale or ARGB value. + * See notes attached to the fill() function. + */ + public void stroke(int rgb) { +// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { // see above +// stroke((float) rgb); +// +// } else { +// colorCalcARGB(rgb, colorModeA); +// strokeFromCalc(); +// } + colorCalc(rgb); + strokeFromCalc(); + } + + + public void stroke(int rgb, float alpha) { +// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { +// stroke((float) rgb, alpha); +// +// } else { +// colorCalcARGB(rgb, alpha); +// strokeFromCalc(); +// } + colorCalc(rgb, alpha); + strokeFromCalc(); + } + + + public void stroke(float gray) { + colorCalc(gray); + strokeFromCalc(); + } + + + public void stroke(float gray, float alpha) { + colorCalc(gray, alpha); + strokeFromCalc(); + } + + + public void stroke(float x, float y, float z) { + colorCalc(x, y, z); + strokeFromCalc(); + } + + + public void stroke(float x, float y, float z, float a) { + colorCalc(x, y, z, a); + strokeFromCalc(); + } + + + protected void strokeFromCalc() { + stroke = true; + strokeR = calcR; + strokeG = calcG; + strokeB = calcB; + strokeA = calcA; + strokeRi = calcRi; + strokeGi = calcGi; + strokeBi = calcBi; + strokeAi = calcAi; + strokeColor = calcColor; + strokeAlpha = calcAlpha; + } + + + + ////////////////////////////////////////////////////////////// + + // TINT COLOR + + + public void noTint() { + tint = false; + } + + + /** + * Set the tint to either a grayscale or ARGB value. + */ + public void tint(int rgb) { +// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { +// tint((float) rgb); +// +// } else { +// colorCalcARGB(rgb, colorModeA); +// tintFromCalc(); +// } + colorCalc(rgb); + tintFromCalc(); + } + + public void tint(int rgb, float alpha) { +// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { +// tint((float) rgb, alpha); +// +// } else { +// colorCalcARGB(rgb, alpha); +// tintFromCalc(); +// } + colorCalc(rgb, alpha); + tintFromCalc(); + } + + public void tint(float gray) { + colorCalc(gray); + tintFromCalc(); + } + + + public void tint(float gray, float alpha) { + colorCalc(gray, alpha); + tintFromCalc(); + } + + + public void tint(float x, float y, float z) { + colorCalc(x, y, z); + tintFromCalc(); + } + + + public void tint(float x, float y, float z, float a) { + colorCalc(x, y, z, a); + tintFromCalc(); + } + + + protected void tintFromCalc() { + tint = true; + tintR = calcR; + tintG = calcG; + tintB = calcB; + tintA = calcA; + tintRi = calcRi; + tintGi = calcGi; + tintBi = calcBi; + tintAi = calcAi; + tintColor = calcColor; + tintAlpha = calcAlpha; + } + + + + ////////////////////////////////////////////////////////////// + + // FILL COLOR + + + public void noFill() { + fill = false; + } + + + /** + * Set the fill to either a grayscale value or an ARGB int. + */ + public void fill(int rgb) { +// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { // see above +// fill((float) rgb); +// +// } else { +// colorCalcARGB(rgb, colorModeA); +// fillFromCalc(); +// } + colorCalc(rgb); + fillFromCalc(); + } + + + public void fill(int rgb, float alpha) { +// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { // see above +// fill((float) rgb, alpha); +// +// } else { +// colorCalcARGB(rgb, alpha); +// fillFromCalc(); +// } + colorCalc(rgb, alpha); + fillFromCalc(); + } + + + public void fill(float gray) { + colorCalc(gray); + fillFromCalc(); + } + + + public void fill(float gray, float alpha) { + colorCalc(gray, alpha); + fillFromCalc(); + } + + + public void fill(float x, float y, float z) { + colorCalc(x, y, z); + fillFromCalc(); + } + + + public void fill(float x, float y, float z, float a) { + colorCalc(x, y, z, a); + fillFromCalc(); + } + + + protected void fillFromCalc() { + fill = true; + fillR = calcR; + fillG = calcG; + fillB = calcB; + fillA = calcA; + fillRi = calcRi; + fillGi = calcGi; + fillBi = calcBi; + fillAi = calcAi; + fillColor = calcColor; + fillAlpha = calcAlpha; + } + + + + ////////////////////////////////////////////////////////////// + + // MATERIAL PROPERTIES + + + public void ambient(int rgb) { +// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { +// ambient((float) rgb); +// +// } else { +// colorCalcARGB(rgb, colorModeA); +// ambientFromCalc(); +// } + colorCalc(rgb); + ambientFromCalc(); + } + + + public void ambient(float gray) { + colorCalc(gray); + ambientFromCalc(); + } + + + public void ambient(float x, float y, float z) { + colorCalc(x, y, z); + ambientFromCalc(); + } + + + protected void ambientFromCalc() { + ambientR = calcR; + ambientG = calcG; + ambientB = calcB; + } + + + public void specular(int rgb) { +// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { +// specular((float) rgb); +// +// } else { +// colorCalcARGB(rgb, colorModeA); +// specularFromCalc(); +// } + colorCalc(rgb); + specularFromCalc(); + } + + + public void specular(float gray) { + colorCalc(gray); + specularFromCalc(); + } + + + public void specular(float x, float y, float z) { + colorCalc(x, y, z); + specularFromCalc(); + } + + + protected void specularFromCalc() { + specularR = calcR; + specularG = calcG; + specularB = calcB; + } + + + public void shininess(float shine) { + shininess = shine; + } + + + public void emissive(int rgb) { +// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { +// emissive((float) rgb); +// +// } else { +// colorCalcARGB(rgb, colorModeA); +// emissiveFromCalc(); +// } + colorCalc(rgb); + emissiveFromCalc(); + } + + + public void emissive(float gray) { + colorCalc(gray); + emissiveFromCalc(); + } + + + public void emissive(float x, float y, float z) { + colorCalc(x, y, z); + emissiveFromCalc(); + } + + + protected void emissiveFromCalc() { + emissiveR = calcR; + emissiveG = calcG; + emissiveB = calcB; + } + + + + ////////////////////////////////////////////////////////////// + + // LIGHTS + + // The details of lighting are very implementation-specific, so this base + // class does not handle any details of settings lights. It does however + // display warning messages that the functions are not available. + + + public void lights() { + showMethodWarning("lights"); + } + + public void noLights() { + showMethodWarning("noLights"); + } + + public void ambientLight(float red, float green, float blue) { + showMethodWarning("ambientLight"); + } + + public void ambientLight(float red, float green, float blue, + float x, float y, float z) { + showMethodWarning("ambientLight"); + } + + public void directionalLight(float red, float green, float blue, + float nx, float ny, float nz) { + showMethodWarning("directionalLight"); + } + + public void pointLight(float red, float green, float blue, + float x, float y, float z) { + showMethodWarning("pointLight"); + } + + public void spotLight(float red, float green, float blue, + float x, float y, float z, + float nx, float ny, float nz, + float angle, float concentration) { + showMethodWarning("spotLight"); + } + + public void lightFalloff(float constant, float linear, float quadratic) { + showMethodWarning("lightFalloff"); + } + + public void lightSpecular(float x, float y, float z) { + showMethodWarning("lightSpecular"); + } + + + + ////////////////////////////////////////////////////////////// + + // BACKGROUND + + /** + * Set the background to a gray or ARGB color. + *

+ * For the main drawing surface, the alpha value will be ignored. However, + * alpha can be used on PGraphics objects from createGraphics(). This is + * the only way to set all the pixels partially transparent, for instance. + *

+ * Note that background() should be called before any transformations occur, + * because some implementations may require the current transformation matrix + * to be identity before drawing. + */ + public void background(int rgb) { +// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { +// background((float) rgb); +// +// } else { +// if (format == RGB) { +// rgb |= 0xff000000; // ignore alpha for main drawing surface +// } +// colorCalcARGB(rgb, colorModeA); +// backgroundFromCalc(); +// backgroundImpl(); +// } + colorCalc(rgb); + backgroundFromCalc(); + } + + + /** + * See notes about alpha in background(x, y, z, a). + */ + public void background(int rgb, float alpha) { +// if (format == RGB) { +// background(rgb); // ignore alpha for main drawing surface +// +// } else { +// if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { +// background((float) rgb, alpha); +// +// } else { +// colorCalcARGB(rgb, alpha); +// backgroundFromCalc(); +// backgroundImpl(); +// } +// } + colorCalc(rgb, alpha); + backgroundFromCalc(); + } + + + /** + * Set the background to a grayscale value, based on the + * current colorMode. + */ + public void background(float gray) { + colorCalc(gray); + backgroundFromCalc(); +// backgroundImpl(); + } + + + /** + * See notes about alpha in background(x, y, z, a). + */ + public void background(float gray, float alpha) { + if (format == RGB) { + background(gray); // ignore alpha for main drawing surface + + } else { + colorCalc(gray, alpha); + backgroundFromCalc(); +// backgroundImpl(); + } + } + + + /** + * Set the background to an r, g, b or h, s, b value, + * based on the current colorMode. + */ + public void background(float x, float y, float z) { + colorCalc(x, y, z); + backgroundFromCalc(); +// backgroundImpl(); + } + + + /** + * Clear the background with a color that includes an alpha value. This can + * only be used with objects created by createGraphics(), because the main + * drawing surface cannot be set transparent. + *

+ * It might be tempting to use this function to partially clear the screen + * on each frame, however that's not how this function works. When calling + * background(), the pixels will be replaced with pixels that have that level + * of transparency. To do a semi-transparent overlay, use fill() with alpha + * and draw a rectangle. + */ + public void background(float x, float y, float z, float a) { +// if (format == RGB) { +// background(x, y, z); // don't allow people to set alpha +// +// } else { +// colorCalc(x, y, z, a); +// backgroundFromCalc(); +// backgroundImpl(); +// } + colorCalc(x, y, z, a); + backgroundFromCalc(); + } + + + protected void backgroundFromCalc() { + backgroundR = calcR; + backgroundG = calcG; + backgroundB = calcB; + backgroundA = (format == RGB) ? colorModeA : calcA; + backgroundRi = calcRi; + backgroundGi = calcGi; + backgroundBi = calcBi; + backgroundAi = (format == RGB) ? 255 : calcAi; + backgroundAlpha = (format == RGB) ? false : calcAlpha; + backgroundColor = calcColor; + + backgroundImpl(); + } + + + /** + * Takes an RGB or ARGB image and sets it as the background. + * The width and height of the image must be the same size as the sketch. + * Use image.resize(width, height) to make short work of such a task. + *

+ * Note that even if the image is set as RGB, the high 8 bits of each pixel + * should be set opaque (0xFF000000), because the image data will be copied + * directly to the screen, and non-opaque background images may have strange + * behavior. Using image.filter(OPAQUE) will handle this easily. + *

+ * When using 3D, this will also clear the zbuffer (if it exists). + */ + public void background(PImage image) { + if ((image.width != width) || (image.height != height)) { + throw new RuntimeException(ERROR_BACKGROUND_IMAGE_SIZE); + } + if ((image.format != RGB) && (image.format != ARGB)) { + throw new RuntimeException(ERROR_BACKGROUND_IMAGE_FORMAT); + } + backgroundColor = 0; // just zero it out for images + backgroundImpl(image); + } + + + /** + * Actually set the background image. This is separated from the error + * handling and other semantic goofiness that is shared across renderers. + */ + protected void backgroundImpl(PImage image) { + // blit image to the screen + set(0, 0, image); + } + + + /** + * Actual implementation of clearing the background, now that the + * internal variables for background color have been set. Called by the + * backgroundFromCalc() method, which is what all the other background() + * methods call once the work is done. + */ + protected void backgroundImpl() { + pushStyle(); + pushMatrix(); + resetMatrix(); + fill(backgroundColor); + rect(0, 0, width, height); + popMatrix(); + popStyle(); + } + + + /** + * Callback to handle clearing the background when begin/endRaw is in use. + * Handled as separate function for OpenGL (or other) subclasses that + * override backgroundImpl() but still needs this to work properly. + */ +// protected void backgroundRawImpl() { +// if (raw != null) { +// raw.colorMode(RGB, 1); +// raw.noStroke(); +// raw.fill(backgroundR, backgroundG, backgroundB); +// raw.beginShape(TRIANGLES); +// +// raw.vertex(0, 0); +// raw.vertex(width, 0); +// raw.vertex(0, height); +// +// raw.vertex(width, 0); +// raw.vertex(width, height); +// raw.vertex(0, height); +// +// raw.endShape(); +// } +// } + + + + ////////////////////////////////////////////////////////////// + + // COLOR MODE + + + public void colorMode(int mode) { + colorMode(mode, colorModeX, colorModeY, colorModeZ, colorModeA); + } + + + public void colorMode(int mode, float max) { + colorMode(mode, max, max, max, max); + } + + + /** + * Set the colorMode and the maximum values for (r, g, b) + * or (h, s, b). + *

+ * Note that this doesn't set the maximum for the alpha value, + * which might be confusing if for instance you switched to + *

colorMode(HSB, 360, 100, 100);
+ * because the alpha values were still between 0 and 255. + */ + public void colorMode(int mode, float maxX, float maxY, float maxZ) { + colorMode(mode, maxX, maxY, maxZ, colorModeA); + } + + + public void colorMode(int mode, + float maxX, float maxY, float maxZ, float maxA) { + colorMode = mode; + + colorModeX = maxX; // still needs to be set for hsb + colorModeY = maxY; + colorModeZ = maxZ; + colorModeA = maxA; + + // if color max values are all 1, then no need to scale + colorModeScale = + ((maxA != 1) || (maxX != maxY) || (maxY != maxZ) || (maxZ != maxA)); + + // if color is rgb/0..255 this will make it easier for the + // red() green() etc functions + colorModeDefault = (colorMode == RGB) && + (colorModeA == 255) && (colorModeX == 255) && + (colorModeY == 255) && (colorModeZ == 255); + } + + + + ////////////////////////////////////////////////////////////// + + // COLOR CALCULATIONS + + // Given input values for coloring, these functions will fill the calcXxxx + // variables with values that have been properly filtered through the + // current colorMode settings. + + // Renderers that need to subclass any drawing properties such as fill or + // stroke will usally want to override methods like fillFromCalc (or the + // same for stroke, ambient, etc.) That way the color calcuations are + // covered by this based PGraphics class, leaving only a single function + // to override/implement in the subclass. + + + /** + * Set the fill to either a grayscale value or an ARGB int. + *

+ * The problem with this code is that it has to detect between these two + * situations automatically. This is done by checking to see if the high bits + * (the alpha for 0xAA000000) is set, and if not, whether the color value + * that follows is less than colorModeX (first param passed to colorMode). + *

+ * This auto-detect would break in the following situation: + *

size(256, 256);
+   * for (int i = 0; i < 256; i++) {
+   *   color c = color(0, 0, 0, i);
+   *   stroke(c);
+   *   line(i, 0, i, 256);
+   * }
+ * ...on the first time through the loop, where (i == 0), since the color + * itself is zero (black) then it would appear indistinguishable from code + * that reads "fill(0)". The solution is to use the four parameter versions + * of stroke or fill to more directly specify the desired result. + */ + protected void colorCalc(int rgb) { + if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { + colorCalc((float) rgb); + + } else { + colorCalcARGB(rgb, colorModeA); + } + } + + + protected void colorCalc(int rgb, float alpha) { + if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { // see above + colorCalc((float) rgb, alpha); + + } else { + colorCalcARGB(rgb, alpha); + } + } + + + protected void colorCalc(float gray) { + colorCalc(gray, colorModeA); + } + + + protected void colorCalc(float gray, float alpha) { + if (gray > colorModeX) gray = colorModeX; + if (alpha > colorModeA) alpha = colorModeA; + + if (gray < 0) gray = 0; + if (alpha < 0) alpha = 0; + + calcR = colorModeScale ? (gray / colorModeX) : gray; + calcG = calcR; + calcB = calcR; + calcA = colorModeScale ? (alpha / colorModeA) : alpha; + + calcRi = (int)(calcR*255); calcGi = (int)(calcG*255); + calcBi = (int)(calcB*255); calcAi = (int)(calcA*255); + calcColor = (calcAi << 24) | (calcRi << 16) | (calcGi << 8) | calcBi; + calcAlpha = (calcAi != 255); + } + + + protected void colorCalc(float x, float y, float z) { + colorCalc(x, y, z, colorModeA); + } + + + protected void colorCalc(float x, float y, float z, float a) { + if (x > colorModeX) x = colorModeX; + if (y > colorModeY) y = colorModeY; + if (z > colorModeZ) z = colorModeZ; + if (a > colorModeA) a = colorModeA; + + if (x < 0) x = 0; + if (y < 0) y = 0; + if (z < 0) z = 0; + if (a < 0) a = 0; + + switch (colorMode) { + case RGB: + if (colorModeScale) { + calcR = x / colorModeX; + calcG = y / colorModeY; + calcB = z / colorModeZ; + calcA = a / colorModeA; + } else { + calcR = x; calcG = y; calcB = z; calcA = a; + } + break; + + case HSB: + x /= colorModeX; // h + y /= colorModeY; // s + z /= colorModeZ; // b + + calcA = colorModeScale ? (a/colorModeA) : a; + + if (y == 0) { // saturation == 0 + calcR = calcG = calcB = z; + + } else { + float which = (x - (int)x) * 6.0f; + float f = which - (int)which; + float p = z * (1.0f - y); + float q = z * (1.0f - y * f); + float t = z * (1.0f - (y * (1.0f - f))); + + switch ((int)which) { + case 0: calcR = z; calcG = t; calcB = p; break; + case 1: calcR = q; calcG = z; calcB = p; break; + case 2: calcR = p; calcG = z; calcB = t; break; + case 3: calcR = p; calcG = q; calcB = z; break; + case 4: calcR = t; calcG = p; calcB = z; break; + case 5: calcR = z; calcG = p; calcB = q; break; + } + } + break; + } + calcRi = (int)(255*calcR); calcGi = (int)(255*calcG); + calcBi = (int)(255*calcB); calcAi = (int)(255*calcA); + calcColor = (calcAi << 24) | (calcRi << 16) | (calcGi << 8) | calcBi; + calcAlpha = (calcAi != 255); + } + + + /** + * Unpacks AARRGGBB color for direct use with colorCalc. + *

+ * Handled here with its own function since this is indepenent + * of the color mode. + *

+ * Strangely the old version of this code ignored the alpha + * value. not sure if that was a bug or what. + *

+ * Note, no need for a bounds check since it's a 32 bit number. + */ + protected void colorCalcARGB(int argb, float alpha) { + if (alpha == colorModeA) { + calcAi = (argb >> 24) & 0xff; + calcColor = argb; + } else { + calcAi = (int) (((argb >> 24) & 0xff) * (alpha / colorModeA)); + calcColor = (calcAi << 24) | (argb & 0xFFFFFF); + } + calcRi = (argb >> 16) & 0xff; + calcGi = (argb >> 8) & 0xff; + calcBi = argb & 0xff; + calcA = (float)calcAi / 255.0f; + calcR = (float)calcRi / 255.0f; + calcG = (float)calcGi / 255.0f; + calcB = (float)calcBi / 255.0f; + calcAlpha = (calcAi != 255); + } + + + + ////////////////////////////////////////////////////////////// + + // COLOR DATATYPE STUFFING + + // The 'color' primitive type in Processing syntax is in fact a 32-bit int. + // These functions handle stuffing color values into a 32-bit cage based + // on the current colorMode settings. + + // These functions are really slow (because they take the current colorMode + // into account), but they're easy to use. Advanced users can write their + // own bit shifting operations to setup 'color' data types. + + + public final int color(int gray) { // ignore + if (((gray & 0xff000000) == 0) && (gray <= colorModeX)) { + if (colorModeDefault) { + // bounds checking to make sure the numbers aren't to high or low + if (gray > 255) gray = 255; else if (gray < 0) gray = 0; + return 0xff000000 | (gray << 16) | (gray << 8) | gray; + } else { + colorCalc(gray); + } + } else { + colorCalcARGB(gray, colorModeA); + } + return calcColor; + } + + + public final int color(float gray) { // ignore + colorCalc(gray); + return calcColor; + } + + + /** + * @param gray can be packed ARGB or a gray in this case + */ + public final int color(int gray, int alpha) { // ignore + if (colorModeDefault) { + // bounds checking to make sure the numbers aren't to high or low + if (gray > 255) gray = 255; else if (gray < 0) gray = 0; + if (alpha > 255) alpha = 255; else if (alpha < 0) alpha = 0; + + return ((alpha & 0xff) << 24) | (gray << 16) | (gray << 8) | gray; + } + colorCalc(gray, alpha); + return calcColor; + } + + + /** + * @param rgb can be packed ARGB or a gray in this case + */ + public final int color(int rgb, float alpha) { // ignore + if (((rgb & 0xff000000) == 0) && (rgb <= colorModeX)) { + colorCalc(rgb, alpha); + } else { + colorCalcARGB(rgb, alpha); + } + return calcColor; + } + + + public final int color(float gray, float alpha) { // ignore + colorCalc(gray, alpha); + return calcColor; + } + + + public final int color(int x, int y, int z) { // ignore + if (colorModeDefault) { + // bounds checking to make sure the numbers aren't to high or low + if (x > 255) x = 255; else if (x < 0) x = 0; + if (y > 255) y = 255; else if (y < 0) y = 0; + if (z > 255) z = 255; else if (z < 0) z = 0; + + return 0xff000000 | (x << 16) | (y << 8) | z; + } + colorCalc(x, y, z); + return calcColor; + } + + + public final int color(float x, float y, float z) { // ignore + colorCalc(x, y, z); + return calcColor; + } + + + public final int color(int x, int y, int z, int a) { // ignore + if (colorModeDefault) { + // bounds checking to make sure the numbers aren't to high or low + if (a > 255) a = 255; else if (a < 0) a = 0; + if (x > 255) x = 255; else if (x < 0) x = 0; + if (y > 255) y = 255; else if (y < 0) y = 0; + if (z > 255) z = 255; else if (z < 0) z = 0; + + return (a << 24) | (x << 16) | (y << 8) | z; + } + colorCalc(x, y, z, a); + return calcColor; + } + + + public final int color(float x, float y, float z, float a) { // ignore + colorCalc(x, y, z, a); + return calcColor; + } + + + + ////////////////////////////////////////////////////////////// + + // COLOR DATATYPE EXTRACTION + + // Vee have veys of making the colors talk. + + + public final float alpha(int what) { + float c = (what >> 24) & 0xff; + if (colorModeA == 255) return c; + return (c / 255.0f) * colorModeA; + } + + + public final float red(int what) { + float c = (what >> 16) & 0xff; + if (colorModeDefault) return c; + return (c / 255.0f) * colorModeX; + } + + + public final float green(int what) { + float c = (what >> 8) & 0xff; + if (colorModeDefault) return c; + return (c / 255.0f) * colorModeY; + } + + + public final float blue(int what) { + float c = (what) & 0xff; + if (colorModeDefault) return c; + return (c / 255.0f) * colorModeZ; + } + + + public final float hue(int what) { + if (what != cacheHsbKey) { + Color.RGBtoHSB((what >> 16) & 0xff, (what >> 8) & 0xff, + what & 0xff, cacheHsbValue); + cacheHsbKey = what; + } + return cacheHsbValue[0] * colorModeX; + } + + + public final float saturation(int what) { + if (what != cacheHsbKey) { + Color.RGBtoHSB((what >> 16) & 0xff, (what >> 8) & 0xff, + what & 0xff, cacheHsbValue); + cacheHsbKey = what; + } + return cacheHsbValue[1] * colorModeY; + } + + + public final float brightness(int what) { + if (what != cacheHsbKey) { + Color.RGBtoHSB((what >> 16) & 0xff, (what >> 8) & 0xff, + what & 0xff, cacheHsbValue); + cacheHsbKey = what; + } + return cacheHsbValue[2] * colorModeZ; + } + + + + ////////////////////////////////////////////////////////////// + + // COLOR DATATYPE INTERPOLATION + + // Against our better judgement. + + + /** + * Interpolate between two colors, using the current color mode. + */ + public int lerpColor(int c1, int c2, float amt) { + return lerpColor(c1, c2, amt, colorMode); + } + + static float[] lerpColorHSB1; + static float[] lerpColorHSB2; + + /** + * Interpolate between two colors. Like lerp(), but for the + * individual color components of a color supplied as an int value. + */ + static public int lerpColor(int c1, int c2, float amt, int mode) { + if (mode == RGB) { + float a1 = ((c1 >> 24) & 0xff); + float r1 = (c1 >> 16) & 0xff; + float g1 = (c1 >> 8) & 0xff; + float b1 = c1 & 0xff; + float a2 = (c2 >> 24) & 0xff; + float r2 = (c2 >> 16) & 0xff; + float g2 = (c2 >> 8) & 0xff; + float b2 = c2 & 0xff; + + return (((int) (a1 + (a2-a1)*amt) << 24) | + ((int) (r1 + (r2-r1)*amt) << 16) | + ((int) (g1 + (g2-g1)*amt) << 8) | + ((int) (b1 + (b2-b1)*amt))); + + } else if (mode == HSB) { + if (lerpColorHSB1 == null) { + lerpColorHSB1 = new float[3]; + lerpColorHSB2 = new float[3]; + } + + float a1 = (c1 >> 24) & 0xff; + float a2 = (c2 >> 24) & 0xff; + int alfa = ((int) (a1 + (a2-a1)*amt)) << 24; + + Color.RGBtoHSB((c1 >> 16) & 0xff, (c1 >> 8) & 0xff, c1 & 0xff, + lerpColorHSB1); + Color.RGBtoHSB((c2 >> 16) & 0xff, (c2 >> 8) & 0xff, c2 & 0xff, + lerpColorHSB2); + + /* If mode is HSB, this will take the shortest path around the + * color wheel to find the new color. For instance, red to blue + * will go red violet blue (backwards in hue space) rather than + * cycling through ROYGBIV. + */ + // Disabling rollover (wasn't working anyway) for 0126. + // Otherwise it makes full spectrum scale impossible for + // those who might want it...in spite of how despicable + // a full spectrum scale might be. + // roll around when 0.9 to 0.1 + // more than 0.5 away means that it should roll in the other direction + /* + float h1 = lerpColorHSB1[0]; + float h2 = lerpColorHSB2[0]; + if (Math.abs(h1 - h2) > 0.5f) { + if (h1 > h2) { + // i.e. h1 is 0.7, h2 is 0.1 + h2 += 1; + } else { + // i.e. h1 is 0.1, h2 is 0.7 + h1 += 1; + } + } + float ho = (PApplet.lerp(lerpColorHSB1[0], lerpColorHSB2[0], amt)) % 1.0f; + */ + float ho = PApplet.lerp(lerpColorHSB1[0], lerpColorHSB2[0], amt); + float so = PApplet.lerp(lerpColorHSB1[1], lerpColorHSB2[1], amt); + float bo = PApplet.lerp(lerpColorHSB1[2], lerpColorHSB2[2], amt); + + return alfa | (Color.HSBtoRGB(ho, so, bo) & 0xFFFFFF); + } + return 0; + } + + + + ////////////////////////////////////////////////////////////// + + // BEGINRAW/ENDRAW + + + /** + * Record individual lines and triangles by echoing them to another renderer. + */ + public void beginRaw(PGraphics rawGraphics) { // ignore + this.raw = rawGraphics; + rawGraphics.beginDraw(); + } + + + public void endRaw() { // ignore + if (raw != null) { + // for 3D, need to flush any geometry that's been stored for sorting + // (particularly if the ENABLE_DEPTH_SORT hint is set) + flush(); + + // just like beginDraw, this will have to be called because + // endDraw() will be happening outside of draw() + raw.endDraw(); + raw.dispose(); + raw = null; + } + } + + + + ////////////////////////////////////////////////////////////// + + // WARNINGS and EXCEPTIONS + + + static protected HashMap warnings; + + + /** + * Show a renderer error, and keep track of it so that it's only shown once. + * @param msg the error message (which will be stored for later comparison) + */ + static public void showWarning(String msg) { // ignore + if (warnings == null) { + warnings = new HashMap(); + } + if (!warnings.containsKey(msg)) { + System.err.println(msg); + warnings.put(msg, new Object()); + } + } + + + /** + * Display a warning that the specified method is only available with 3D. + * @param method The method name (no parentheses) + */ + static protected void showDepthWarning(String method) { + showWarning(method + "() can only be used with a renderer that " + + "supports 3D, such as P3D or OPENGL."); + } + + + /** + * Display a warning that the specified method that takes x, y, z parameters + * can only be used with x and y parameters in this renderer. + * @param method The method name (no parentheses) + */ + static protected void showDepthWarningXYZ(String method) { + showWarning(method + "() with x, y, and z coordinates " + + "can only be used with a renderer that " + + "supports 3D, such as P3D or OPENGL. " + + "Use a version without a z-coordinate instead."); + } + + + /** + * Display a warning that the specified method is simply unavailable. + */ + static protected void showMethodWarning(String method) { + showWarning(method + "() is not available with this renderer."); + } + + + /** + * Error that a particular variation of a method is unavailable (even though + * other variations are). For instance, if vertex(x, y, u, v) is not + * available, but vertex(x, y) is just fine. + */ + static protected void showVariationWarning(String str) { + showWarning(str + " is not available with this renderer."); + } + + + /** + * Display a warning that the specified method is not implemented, meaning + * that it could be either a completely missing function, although other + * variations of it may still work properly. + */ + static protected void showMissingWarning(String method) { + showWarning(method + "(), or this particular variation of it, " + + "is not available with this renderer."); + } + + + /** + * Show an renderer-related exception that halts the program. Currently just + * wraps the message as a RuntimeException and throws it, but might do + * something more specific might be used in the future. + */ + static public void showException(String msg) { // ignore + throw new RuntimeException(msg); + } + + + /** + * Throw an exeption that halts the program because textFont() has not been + * used prior to the specified method. + */ + static protected void showTextFontException(String method) { + throw new RuntimeException("Use textFont() before " + method + "()"); + } + + + + ////////////////////////////////////////////////////////////// + + // RENDERER SUPPORT QUERIES + + + /** + * Return true if this renderer should be drawn to the screen. Defaults to + * returning true, since nearly all renderers are on-screen beasts. But can + * be overridden for subclasses like PDF so that a window doesn't open up. + *

+ * A better name? showFrame, displayable, isVisible, visible, shouldDisplay, + * what to call this? + */ + public boolean displayable() { + return true; + } + + + /** + * Return true if this renderer supports 2D drawing. Defaults to true. + */ + public boolean is2D() { + return true; + } + + + /** + * Return true if this renderer supports 2D drawing. Defaults to true. + */ + public boolean is3D() { + return false; + } +} diff --git a/core/preproc/demo/PImage.java b/core/preproc/demo/PImage.java new file mode 100644 index 000000000..9dd126e4b --- /dev/null +++ b/core/preproc/demo/PImage.java @@ -0,0 +1,2713 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2004-08 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.core; + +import java.awt.image.*; +import java.io.*; +import java.util.HashMap; + +import javax.imageio.ImageIO; + + +/** + * Storage class for pixel data. This is the base class for most image and + * pixel information, such as PGraphics and the video library classes. + *

+ * Code for copying, resizing, scaling, and blending contributed + * by toxi. + *

+ */ +public class PImage implements PConstants, Cloneable { + + /** + * Format for this image, one of RGB, ARGB or ALPHA. + * note that RGB images still require 0xff in the high byte + * because of how they'll be manipulated by other functions + */ + public int format; + + public int[] pixels; + public int width, height; + + /** + * Path to parent object that will be used with save(). + * This prevents users from needing savePath() to use PImage.save(). + */ + public PApplet parent; + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + /** for subclasses that need to store info about the image */ + protected HashMap cacheMap; + + + /** modified portion of the image */ + protected boolean modified; + protected int mx1, my1, mx2, my2; + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + // private fields + private int fracU, ifU, fracV, ifV, u1, u2, v1, v2, sX, sY, iw, iw1, ih1; + private int ul, ll, ur, lr, cUL, cLL, cUR, cLR; + private int srcXOffset, srcYOffset; + private int r, g, b, a; + private int[] srcBuffer; + + // fixed point precision is limited to 15 bits!! + static final int PRECISIONB = 15; + static final int PRECISIONF = 1 << PRECISIONB; + static final int PREC_MAXVAL = PRECISIONF-1; + static final int PREC_ALPHA_SHIFT = 24-PRECISIONB; + static final int PREC_RED_SHIFT = 16-PRECISIONB; + + // internal kernel stuff for the gaussian blur filter + private int blurRadius; + private int blurKernelSize; + private int[] blurKernel; + private int[][] blurMult; + + + ////////////////////////////////////////////////////////////// + + + /** + * Create an empty image object, set its format to RGB. + * The pixel array is not allocated. + */ + public PImage() { + format = ARGB; // default to ARGB images for release 0116 +// cache = null; + } + + + /** + * Create a new RGB (alpha ignored) image of a specific size. + * All pixels are set to zero, meaning black, but since the + * alpha is zero, it will be transparent. + */ + public PImage(int width, int height) { + init(width, height, RGB); + + // toxi: is it maybe better to init the image with max alpha enabled? + //for(int i=0; i(); + cacheMap.put(parent, storage); + } + + + /** + * Get cache storage data for the specified renderer. Because each renderer + * will cache data in different formats, it's necessary to store cache data + * keyed by the renderer object. Otherwise, attempting to draw the same + * image to both a PGraphicsJava2D and a PGraphicsOpenGL will cause errors. + * @param parent The PGraphics object (or any object, really) associated + * @return data stored for the specified parent + */ + public Object getCache(Object parent) { + if (cacheMap == null) return null; + return cacheMap.get(parent); + } + + + /** + * Remove information associated with this renderer from the cache, if any. + * @param parent The PGraphics object whose cache data should be removed + */ + public void removeCache(Object parent) { + if (cacheMap != null) { + cacheMap.remove(parent); + } + } + + + + ////////////////////////////////////////////////////////////// + + // MARKING IMAGE AS MODIFIED / FOR USE w/ GET/SET + + + public boolean isModified() { // ignore + return modified; + } + + + public void setModified() { // ignore + modified = true; + } + + + public void setModified(boolean m) { // ignore + modified = m; + } + + + /** + * Call this when you want to mess with the pixels[] array. + *

+ * For subclasses where the pixels[] buffer isn't set by default, + * this should copy all data into the pixels[] array + */ + public void loadPixels() { // ignore + } + + + /** + * Call this when finished messing with the pixels[] array. + *

+ * Mark all pixels as needing update. + */ + public void updatePixels() { // ignore + updatePixelsImpl(0, 0, width, height); + } + + + /** + * Mark the pixels in this region as needing an update. + *

+ * This is not currently used by any of the renderers, however the api + * is structured this way in the hope of being able to use this to + * speed things up in the future. + */ + public void updatePixels(int x, int y, int w, int h) { // ignore +// if (imageMode == CORNER) { // x2, y2 are w/h +// x2 += x1; +// y2 += y1; +// +// } else if (imageMode == CENTER) { +// x1 -= x2 / 2; +// y1 -= y2 / 2; +// x2 += x1; +// y2 += y1; +// } + updatePixelsImpl(x, y, w, h); + } + + + protected void updatePixelsImpl(int x, int y, int w, int h) { + int x2 = x + w; + int y2 = y + h; + + if (!modified) { + mx1 = x; + mx2 = x2; + my1 = y; + my2 = y2; + modified = true; + + } else { + if (x < mx1) mx1 = x; + if (x > mx2) mx2 = x; + if (y < my1) my1 = y; + if (y > my2) my2 = y; + + if (x2 < mx1) mx1 = x2; + if (x2 > mx2) mx2 = x2; + if (y2 < my1) my1 = y2; + if (y2 > my2) my2 = y2; + } + } + + + + ////////////////////////////////////////////////////////////// + + // COPYING IMAGE DATA + + + /** + * Duplicate an image, returns new PImage object. + * The pixels[] array for the new object will be unique + * and recopied from the source image. This is implemented as an + * override of Object.clone(). We recommend using get() instead, + * because it prevents you from needing to catch the + * CloneNotSupportedException, and from doing a cast from the result. + */ + public Object clone() throws CloneNotSupportedException { // ignore + PImage c = (PImage) super.clone(); + + // super.clone() will only copy the reference to the pixels + // array, so this will do a proper duplication of it instead. + c.pixels = new int[width * height]; + System.arraycopy(pixels, 0, c.pixels, 0, pixels.length); + + // return the goods + return c; + } + + + /** + * Resize this image to a new width and height. + * Use 0 for wide or high to make that dimension scale proportionally. + */ + public void resize(int wide, int high) { // ignore + // Make sure that the pixels[] array is valid + loadPixels(); + + if (wide <= 0 && high <= 0) { + width = 0; // Gimme a break, don't waste my time + height = 0; + pixels = new int[0]; + + } else { + if (wide == 0) { // Use height to determine relative size + float diff = (float) high / (float) height; + wide = (int) (width * diff); + } else if (high == 0) { // Use the width to determine relative size + float diff = (float) wide / (float) width; + high = (int) (height * diff); + } + PImage temp = new PImage(wide, high, this.format); + temp.copy(this, 0, 0, width, height, 0, 0, wide, high); + this.width = wide; + this.height = high; + this.pixels = temp.pixels; + } + // Mark the pixels array as altered + updatePixels(); + } + + + + ////////////////////////////////////////////////////////////// + + // GET/SET PIXELS + + + /** + * Returns an ARGB "color" type (a packed 32 bit int with the color. + * If the coordinate is outside the image, zero is returned + * (black, but completely transparent). + *

+ * If the image is in RGB format (i.e. on a PVideo object), + * the value will get its high bits set, just to avoid cases where + * they haven't been set already. + *

+ * If the image is in ALPHA format, this returns a white with its + * alpha value set. + *

+ * This function is included primarily for beginners. It is quite + * slow because it has to check to see if the x, y that was provided + * is inside the bounds, and then has to check to see what image + * type it is. If you want things to be more efficient, access the + * pixels[] array directly. + */ + public int get(int x, int y) { + if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) return 0; + + switch (format) { + case RGB: + return pixels[y*width + x] | 0xff000000; + + case ARGB: + return pixels[y*width + x]; + + case ALPHA: + return (pixels[y*width + x] << 24) | 0xffffff; + } + return 0; + } + + + /** + * Grab a subsection of a PImage, and copy it into a fresh PImage. + * As of release 0149, no longer honors imageMode() for the coordinates. + */ + public PImage get(int x, int y, int w, int h) { + /* + if (imageMode == CORNERS) { // if CORNER, do nothing + //x2 += x1; y2 += y1; + // w/h are x2/y2 in this case, bring em down to size + w = (w - x); + h = (h - y); + } else if (imageMode == CENTER) { + x -= w/2; + y -= h/2; + } + */ + + if (x < 0) { + w += x; // clip off the left edge + x = 0; + } + if (y < 0) { + h += y; // clip off some of the height + y = 0; + } + + if (x + w > width) w = width - x; + if (y + h > height) h = height - y; + + return getImpl(x, y, w, h); + } + + + /** + * Internal function to actually handle getting a block of pixels that + * has already been properly cropped to a valid region. That is, x/y/w/h + * are guaranteed to be inside the image space, so the implementation can + * use the fastest possible pixel copying method. + */ + protected PImage getImpl(int x, int y, int w, int h) { + PImage newbie = new PImage(w, h, format); + newbie.parent = parent; + + int index = y*width + x; + int index2 = 0; + for (int row = y; row < y+h; row++) { + System.arraycopy(pixels, index, newbie.pixels, index2, w); + index += width; + index2 += w; + } + return newbie; + } + + + /** + * Returns a copy of this PImage. Equivalent to get(0, 0, width, height). + */ + public PImage get() { + try { + PImage clone = (PImage) clone(); + // don't want to pass this down to the others + // http://dev.processing.org/bugs/show_bug.cgi?id=1245 + clone.cacheMap = null; + return clone; + } catch (CloneNotSupportedException e) { + return null; + } + } + + + /** + * Set a single pixel to the specified color. + */ + public void set(int x, int y, int c) { + if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) return; + pixels[y*width + x] = c; + updatePixelsImpl(x, y, x+1, y+1); // slow? + } + + + /** + * Efficient method of drawing an image's pixels directly to this surface. + * No variations are employed, meaning that any scale, tint, or imageMode + * settings will be ignored. + */ + public void set(int x, int y, PImage src) { + int sx = 0; + int sy = 0; + int sw = src.width; + int sh = src.height; + +// if (imageMode == CENTER) { +// x -= src.width/2; +// y -= src.height/2; +// } + if (x < 0) { // off left edge + sx -= x; + sw += x; + x = 0; + } + if (y < 0) { // off top edge + sy -= y; + sh += y; + y = 0; + } + if (x + sw > width) { // off right edge + sw = width - x; + } + if (y + sh > height) { // off bottom edge + sh = height - y; + } + + // this could be nonexistant + if ((sw <= 0) || (sh <= 0)) return; + + setImpl(x, y, sx, sy, sw, sh, src); + } + + + /** + * Internal function to actually handle setting a block of pixels that + * has already been properly cropped from the image to a valid region. + */ + protected void setImpl(int dx, int dy, int sx, int sy, int sw, int sh, + PImage src) { + int srcOffset = sy * src.width + sx; + int dstOffset = dy * width + dx; + + for (int y = sy; y < sy + sh; y++) { + System.arraycopy(src.pixels, srcOffset, pixels, dstOffset, sw); + srcOffset += src.width; + dstOffset += width; + } + updatePixelsImpl(sx, sy, sx+sw, sy+sh); + } + + + + ////////////////////////////////////////////////////////////// + + // ALPHA CHANNEL + + + /** + * Set alpha channel for an image. Black colors in the source + * image will make the destination image completely transparent, + * and white will make things fully opaque. Gray values will + * be in-between steps. + *

+ * Strictly speaking the "blue" value from the source image is + * used as the alpha color. For a fully grayscale image, this + * is correct, but for a color image it's not 100% accurate. + * For a more accurate conversion, first use filter(GRAY) + * which will make the image into a "correct" grayscake by + * performing a proper luminance-based conversion. + */ + public void mask(int alpha[]) { + loadPixels(); + // don't execute if mask image is different size + if (alpha.length != pixels.length) { + throw new RuntimeException("The PImage used with mask() must be " + + "the same size as the applet."); + } + for (int i = 0; i < pixels.length; i++) { + pixels[i] = ((alpha[i] & 0xff) << 24) | (pixels[i] & 0xffffff); + } + format = ARGB; + updatePixels(); + } + + + /** + * Set alpha channel for an image using another image as the source. + */ + public void mask(PImage alpha) { + mask(alpha.pixels); + } + + + + ////////////////////////////////////////////////////////////// + + // IMAGE FILTERS + + + /** + * Method to apply a variety of basic filters to this image. + *

+ *

+ * Luminance conversion code contributed by + * toxi + *

+ * Gaussian blur code contributed by + * Mario Klingemann + */ + public void filter(int kind) { + loadPixels(); + + switch (kind) { + case BLUR: + // TODO write basic low-pass filter blur here + // what does photoshop do on the edges with this guy? + // better yet.. why bother? just use gaussian with radius 1 + filter(BLUR, 1); + break; + + case GRAY: + if (format == ALPHA) { + // for an alpha image, convert it to an opaque grayscale + for (int i = 0; i < pixels.length; i++) { + int col = 255 - pixels[i]; + pixels[i] = 0xff000000 | (col << 16) | (col << 8) | col; + } + format = RGB; + + } else { + // Converts RGB image data into grayscale using + // weighted RGB components, and keeps alpha channel intact. + // [toxi 040115] + for (int i = 0; i < pixels.length; i++) { + int col = pixels[i]; + // luminance = 0.3*red + 0.59*green + 0.11*blue + // 0.30 * 256 = 77 + // 0.59 * 256 = 151 + // 0.11 * 256 = 28 + int lum = (77*(col>>16&0xff) + 151*(col>>8&0xff) + 28*(col&0xff))>>8; + pixels[i] = (col & ALPHA_MASK) | lum<<16 | lum<<8 | lum; + } + } + break; + + case INVERT: + for (int i = 0; i < pixels.length; i++) { + //pixels[i] = 0xff000000 | + pixels[i] ^= 0xffffff; + } + break; + + case POSTERIZE: + throw new RuntimeException("Use filter(POSTERIZE, int levels) " + + "instead of filter(POSTERIZE)"); + + case RGB: + for (int i = 0; i < pixels.length; i++) { + pixels[i] |= 0xff000000; + } + format = RGB; + break; + + case THRESHOLD: + filter(THRESHOLD, 0.5f); + break; + + // [toxi20050728] added new filters + case ERODE: + dilate(true); + break; + + case DILATE: + dilate(false); + break; + } + updatePixels(); // mark as modified + } + + + /** + * Method to apply a variety of basic filters to this image. + * These filters all take a parameter. + *

+ *

+ * Gaussian blur code contributed by + * Mario Klingemann + * and later updated by toxi for better speed. + */ + public void filter(int kind, float param) { + loadPixels(); + + switch (kind) { + case BLUR: + if (format == ALPHA) + blurAlpha(param); + else if (format == ARGB) + blurARGB(param); + else + blurRGB(param); + break; + + case GRAY: + throw new RuntimeException("Use filter(GRAY) instead of " + + "filter(GRAY, param)"); + + case INVERT: + throw new RuntimeException("Use filter(INVERT) instead of " + + "filter(INVERT, param)"); + + case OPAQUE: + throw new RuntimeException("Use filter(OPAQUE) instead of " + + "filter(OPAQUE, param)"); + + case POSTERIZE: + int levels = (int)param; + if ((levels < 2) || (levels > 255)) { + throw new RuntimeException("Levels must be between 2 and 255 for " + + "filter(POSTERIZE, levels)"); + } + int levels1 = levels - 1; + for (int i = 0; i < pixels.length; i++) { + int rlevel = (pixels[i] >> 16) & 0xff; + int glevel = (pixels[i] >> 8) & 0xff; + int blevel = pixels[i] & 0xff; + rlevel = (((rlevel * levels) >> 8) * 255) / levels1; + glevel = (((glevel * levels) >> 8) * 255) / levels1; + blevel = (((blevel * levels) >> 8) * 255) / levels1; + pixels[i] = ((0xff000000 & pixels[i]) | + (rlevel << 16) | + (glevel << 8) | + blevel); + } + break; + + case THRESHOLD: // greater than or equal to the threshold + int thresh = (int) (param * 255); + for (int i = 0; i < pixels.length; i++) { + int max = Math.max((pixels[i] & RED_MASK) >> 16, + Math.max((pixels[i] & GREEN_MASK) >> 8, + (pixels[i] & BLUE_MASK))); + pixels[i] = (pixels[i] & ALPHA_MASK) | + ((max < thresh) ? 0x000000 : 0xffffff); + } + break; + + // [toxi20050728] added new filters + case ERODE: + throw new RuntimeException("Use filter(ERODE) instead of " + + "filter(ERODE, param)"); + case DILATE: + throw new RuntimeException("Use filter(DILATE) instead of " + + "filter(DILATE, param)"); + } + updatePixels(); // mark as modified + } + + + /** + * Optimized code for building the blur kernel. + * further optimized blur code (approx. 15% for radius=20) + * bigger speed gains for larger radii (~30%) + * added support for various image types (ALPHA, RGB, ARGB) + * [toxi 050728] + */ + protected void buildBlurKernel(float r) { + int radius = (int) (r * 3.5f); + radius = (radius < 1) ? 1 : ((radius < 248) ? radius : 248); + if (blurRadius != radius) { + blurRadius = radius; + blurKernelSize = 1 + blurRadius<<1; + blurKernel = new int[blurKernelSize]; + blurMult = new int[blurKernelSize][256]; + + int bk,bki; + int[] bm,bmi; + + for (int i = 1, radiusi = radius - 1; i < radius; i++) { + blurKernel[radius+i] = blurKernel[radiusi] = bki = radiusi * radiusi; + bm=blurMult[radius+i]; + bmi=blurMult[radiusi--]; + for (int j = 0; j < 256; j++) + bm[j] = bmi[j] = bki*j; + } + bk = blurKernel[radius] = radius * radius; + bm = blurMult[radius]; + for (int j = 0; j < 256; j++) + bm[j] = bk*j; + } + } + + + protected void blurAlpha(float r) { + int sum, cb; + int read, ri, ym, ymi, bk0; + int b2[] = new int[pixels.length]; + int yi = 0; + + buildBlurKernel(r); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + //cb = cg = cr = sum = 0; + cb = sum = 0; + read = x - blurRadius; + if (read<0) { + bk0=-read; + read=0; + } else { + if (read >= width) + break; + bk0=0; + } + for (int i = bk0; i < blurKernelSize; i++) { + if (read >= width) + break; + int c = pixels[read + yi]; + int[] bm=blurMult[i]; + cb += bm[c & BLUE_MASK]; + sum += blurKernel[i]; + read++; + } + ri = yi + x; + b2[ri] = cb / sum; + } + yi += width; + } + + yi = 0; + ym=-blurRadius; + ymi=ym*width; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + //cb = cg = cr = sum = 0; + cb = sum = 0; + if (ym<0) { + bk0 = ri = -ym; + read = x; + } else { + if (ym >= height) + break; + bk0 = 0; + ri = ym; + read = x + ymi; + } + for (int i = bk0; i < blurKernelSize; i++) { + if (ri >= height) + break; + int[] bm=blurMult[i]; + cb += bm[b2[read]]; + sum += blurKernel[i]; + ri++; + read += width; + } + pixels[x+yi] = (cb/sum); + } + yi += width; + ymi += width; + ym++; + } + } + + + protected void blurRGB(float r) { + int sum, cr, cg, cb; //, k; + int /*pixel,*/ read, ri, /*roff,*/ ym, ymi, /*riw,*/ bk0; + int r2[] = new int[pixels.length]; + int g2[] = new int[pixels.length]; + int b2[] = new int[pixels.length]; + int yi = 0; + + buildBlurKernel(r); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + cb = cg = cr = sum = 0; + read = x - blurRadius; + if (read<0) { + bk0=-read; + read=0; + } else { + if (read >= width) + break; + bk0=0; + } + for (int i = bk0; i < blurKernelSize; i++) { + if (read >= width) + break; + int c = pixels[read + yi]; + int[] bm=blurMult[i]; + cr += bm[(c & RED_MASK) >> 16]; + cg += bm[(c & GREEN_MASK) >> 8]; + cb += bm[c & BLUE_MASK]; + sum += blurKernel[i]; + read++; + } + ri = yi + x; + r2[ri] = cr / sum; + g2[ri] = cg / sum; + b2[ri] = cb / sum; + } + yi += width; + } + + yi = 0; + ym=-blurRadius; + ymi=ym*width; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + cb = cg = cr = sum = 0; + if (ym<0) { + bk0 = ri = -ym; + read = x; + } else { + if (ym >= height) + break; + bk0 = 0; + ri = ym; + read = x + ymi; + } + for (int i = bk0; i < blurKernelSize; i++) { + if (ri >= height) + break; + int[] bm=blurMult[i]; + cr += bm[r2[read]]; + cg += bm[g2[read]]; + cb += bm[b2[read]]; + sum += blurKernel[i]; + ri++; + read += width; + } + pixels[x+yi] = 0xff000000 | (cr/sum)<<16 | (cg/sum)<<8 | (cb/sum); + } + yi += width; + ymi += width; + ym++; + } + } + + + protected void blurARGB(float r) { + int sum, cr, cg, cb, ca; + int /*pixel,*/ read, ri, /*roff,*/ ym, ymi, /*riw,*/ bk0; + int wh = pixels.length; + int r2[] = new int[wh]; + int g2[] = new int[wh]; + int b2[] = new int[wh]; + int a2[] = new int[wh]; + int yi = 0; + + buildBlurKernel(r); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + cb = cg = cr = ca = sum = 0; + read = x - blurRadius; + if (read<0) { + bk0=-read; + read=0; + } else { + if (read >= width) + break; + bk0=0; + } + for (int i = bk0; i < blurKernelSize; i++) { + if (read >= width) + break; + int c = pixels[read + yi]; + int[] bm=blurMult[i]; + ca += bm[(c & ALPHA_MASK) >>> 24]; + cr += bm[(c & RED_MASK) >> 16]; + cg += bm[(c & GREEN_MASK) >> 8]; + cb += bm[c & BLUE_MASK]; + sum += blurKernel[i]; + read++; + } + ri = yi + x; + a2[ri] = ca / sum; + r2[ri] = cr / sum; + g2[ri] = cg / sum; + b2[ri] = cb / sum; + } + yi += width; + } + + yi = 0; + ym=-blurRadius; + ymi=ym*width; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + cb = cg = cr = ca = sum = 0; + if (ym<0) { + bk0 = ri = -ym; + read = x; + } else { + if (ym >= height) + break; + bk0 = 0; + ri = ym; + read = x + ymi; + } + for (int i = bk0; i < blurKernelSize; i++) { + if (ri >= height) + break; + int[] bm=blurMult[i]; + ca += bm[a2[read]]; + cr += bm[r2[read]]; + cg += bm[g2[read]]; + cb += bm[b2[read]]; + sum += blurKernel[i]; + ri++; + read += width; + } + pixels[x+yi] = (ca/sum)<<24 | (cr/sum)<<16 | (cg/sum)<<8 | (cb/sum); + } + yi += width; + ymi += width; + ym++; + } + } + + + /** + * Generic dilate/erode filter using luminance values + * as decision factor. [toxi 050728] + */ + protected void dilate(boolean isInverted) { + int currIdx=0; + int maxIdx=pixels.length; + int[] out=new int[maxIdx]; + + if (!isInverted) { + // erosion (grow light areas) + while (currIdx=maxRowIdx) + idxRight=currIdx; + if (idxUp<0) + idxUp=0; + if (idxDown>=maxIdx) + idxDown=currIdx; + + int colUp=pixels[idxUp]; + int colLeft=pixels[idxLeft]; + int colDown=pixels[idxDown]; + int colRight=pixels[idxRight]; + + // compute luminance + int currLum = + 77*(colOrig>>16&0xff) + 151*(colOrig>>8&0xff) + 28*(colOrig&0xff); + int lumLeft = + 77*(colLeft>>16&0xff) + 151*(colLeft>>8&0xff) + 28*(colLeft&0xff); + int lumRight = + 77*(colRight>>16&0xff) + 151*(colRight>>8&0xff) + 28*(colRight&0xff); + int lumUp = + 77*(colUp>>16&0xff) + 151*(colUp>>8&0xff) + 28*(colUp&0xff); + int lumDown = + 77*(colDown>>16&0xff) + 151*(colDown>>8&0xff) + 28*(colDown&0xff); + + if (lumLeft>currLum) { + colOut=colLeft; + currLum=lumLeft; + } + if (lumRight>currLum) { + colOut=colRight; + currLum=lumRight; + } + if (lumUp>currLum) { + colOut=colUp; + currLum=lumUp; + } + if (lumDown>currLum) { + colOut=colDown; + currLum=lumDown; + } + out[currIdx++]=colOut; + } + } + } else { + // dilate (grow dark areas) + while (currIdx=maxRowIdx) + idxRight=currIdx; + if (idxUp<0) + idxUp=0; + if (idxDown>=maxIdx) + idxDown=currIdx; + + int colUp=pixels[idxUp]; + int colLeft=pixels[idxLeft]; + int colDown=pixels[idxDown]; + int colRight=pixels[idxRight]; + + // compute luminance + int currLum = + 77*(colOrig>>16&0xff) + 151*(colOrig>>8&0xff) + 28*(colOrig&0xff); + int lumLeft = + 77*(colLeft>>16&0xff) + 151*(colLeft>>8&0xff) + 28*(colLeft&0xff); + int lumRight = + 77*(colRight>>16&0xff) + 151*(colRight>>8&0xff) + 28*(colRight&0xff); + int lumUp = + 77*(colUp>>16&0xff) + 151*(colUp>>8&0xff) + 28*(colUp&0xff); + int lumDown = + 77*(colDown>>16&0xff) + 151*(colDown>>8&0xff) + 28*(colDown&0xff); + + if (lumLeft + *
  • REPLACE - destination colour equals colour of source pixel: C = A. + * Sometimes called "Normal" or "Copy" in other software. + * + *
  • BLEND - linear interpolation of colours: + * C = A*factor + B + * + *
  • ADD - additive blending with white clip: + * C = min(A*factor + B, 255). + * Clipped to 0..255, Photoshop calls this "Linear Burn", + * and Director calls it "Add Pin". + * + *
  • SUBTRACT - substractive blend with black clip: + * C = max(B - A*factor, 0). + * Clipped to 0..255, Photoshop calls this "Linear Dodge", + * and Director calls it "Subtract Pin". + * + *
  • DARKEST - only the darkest colour succeeds: + * C = min(A*factor, B). + * Illustrator calls this "Darken". + * + *
  • LIGHTEST - only the lightest colour succeeds: + * C = max(A*factor, B). + * Illustrator calls this "Lighten". + * + *
  • DIFFERENCE - subtract colors from underlying image. + * + *
  • EXCLUSION - similar to DIFFERENCE, but less extreme. + * + *
  • MULTIPLY - Multiply the colors, result will always be darker. + * + *
  • SCREEN - Opposite multiply, uses inverse values of the colors. + * + *
  • OVERLAY - A mix of MULTIPLY and SCREEN. Multiplies dark values, + * and screens light values. + * + *
  • HARD_LIGHT - SCREEN when greater than 50% gray, MULTIPLY when lower. + * + *
  • SOFT_LIGHT - Mix of DARKEST and LIGHTEST. + * Works like OVERLAY, but not as harsh. + * + *
  • DODGE - Lightens light tones and increases contrast, ignores darks. + * Called "Color Dodge" in Illustrator and Photoshop. + * + *
  • BURN - Darker areas are applied, increasing contrast, ignores lights. + * Called "Color Burn" in Illustrator and Photoshop. + * + *

    A useful reference for blending modes and their algorithms can be + * found in the SVG + * specification.

    + *

    It is important to note that Processing uses "fast" code, not + * necessarily "correct" code. No biggie, most software does. A nitpicker + * can find numerous "off by 1 division" problems in the blend code where + * >>8 or >>7 is used when strictly speaking + * /255.0 or /127.0 should have been used.

    + *

    For instance, exclusion (not intended for real-time use) reads + * r1 + r2 - ((2 * r1 * r2) / 255) because 255 == 1.0 + * not 256 == 1.0. In other words, (255*255)>>8 is not + * the same as (255*255)/255. But for real-time use the shifts + * are preferrable, and the difference is insignificant for applications + * built with Processing.

    + */ + static public int blendColor(int c1, int c2, int mode) { + switch (mode) { + case REPLACE: return c2; + case BLEND: return blend_blend(c1, c2); + + case ADD: return blend_add_pin(c1, c2); + case SUBTRACT: return blend_sub_pin(c1, c2); + + case LIGHTEST: return blend_lightest(c1, c2); + case DARKEST: return blend_darkest(c1, c2); + + case DIFFERENCE: return blend_difference(c1, c2); + case EXCLUSION: return blend_exclusion(c1, c2); + + case MULTIPLY: return blend_multiply(c1, c2); + case SCREEN: return blend_screen(c1, c2); + + case HARD_LIGHT: return blend_hard_light(c1, c2); + case SOFT_LIGHT: return blend_soft_light(c1, c2); + case OVERLAY: return blend_overlay(c1, c2); + + case DODGE: return blend_dodge(c1, c2); + case BURN: return blend_burn(c1, c2); + } + return 0; + } + + + /** + * Blends one area of this image to another area. + * @see processing.core.PImage#blendColor(int,int,int) + */ + public void blend(int sx, int sy, int sw, int sh, + int dx, int dy, int dw, int dh, int mode) { + blend(this, sx, sy, sw, sh, dx, dy, dw, dh, mode); + } + + + /** + * Copies area of one image into another PImage object. + * @see processing.core.PImage#blendColor(int,int,int) + */ + public void blend(PImage src, + int sx, int sy, int sw, int sh, + int dx, int dy, int dw, int dh, int mode) { + /* + if (imageMode == CORNER) { // if CORNERS, do nothing + sx2 += sx1; + sy2 += sy1; + dx2 += dx1; + dy2 += dy1; + + } else if (imageMode == CENTER) { + sx1 -= sx2 / 2f; + sy1 -= sy2 / 2f; + sx2 += sx1; + sy2 += sy1; + dx1 -= dx2 / 2f; + dy1 -= dy2 / 2f; + dx2 += dx1; + dy2 += dy1; + } + */ + int sx2 = sx + sw; + int sy2 = sy + sh; + int dx2 = dx + dw; + int dy2 = dy + dh; + + loadPixels(); + if (src == this) { + if (intersect(sx, sy, sx2, sy2, dx, dy, dx2, dy2)) { + blit_resize(get(sx, sy, sx2 - sx, sy2 - sy), + 0, 0, sx2 - sx - 1, sy2 - sy - 1, + pixels, width, height, dx, dy, dx2, dy2, mode); + } else { + // same as below, except skip the loadPixels() because it'd be redundant + blit_resize(src, sx, sy, sx2, sy2, + pixels, width, height, dx, dy, dx2, dy2, mode); + } + } else { + src.loadPixels(); + blit_resize(src, sx, sy, sx2, sy2, + pixels, width, height, dx, dy, dx2, dy2, mode); + //src.updatePixels(); + } + updatePixels(); + } + + + /** + * Check to see if two rectangles intersect one another + */ + private boolean intersect(int sx1, int sy1, int sx2, int sy2, + int dx1, int dy1, int dx2, int dy2) { + int sw = sx2 - sx1 + 1; + int sh = sy2 - sy1 + 1; + int dw = dx2 - dx1 + 1; + int dh = dy2 - dy1 + 1; + + if (dx1 < sx1) { + dw += dx1 - sx1; + if (dw > sw) { + dw = sw; + } + } else { + int w = sw + sx1 - dx1; + if (dw > w) { + dw = w; + } + } + if (dy1 < sy1) { + dh += dy1 - sy1; + if (dh > sh) { + dh = sh; + } + } else { + int h = sh + sy1 - dy1; + if (dh > h) { + dh = h; + } + } + return !(dw <= 0 || dh <= 0); + } + + + ////////////////////////////////////////////////////////////// + + + /** + * Internal blitter/resizer/copier from toxi. + * Uses bilinear filtering if smooth() has been enabled + * 'mode' determines the blending mode used in the process. + */ + private void blit_resize(PImage img, + int srcX1, int srcY1, int srcX2, int srcY2, + int[] destPixels, int screenW, int screenH, + int destX1, int destY1, int destX2, int destY2, + int mode) { + if (srcX1 < 0) srcX1 = 0; + if (srcY1 < 0) srcY1 = 0; + if (srcX2 >= img.width) srcX2 = img.width - 1; + if (srcY2 >= img.height) srcY2 = img.height - 1; + + int srcW = srcX2 - srcX1; + int srcH = srcY2 - srcY1; + int destW = destX2 - destX1; + int destH = destY2 - destY1; + + boolean smooth = true; // may as well go with the smoothing these days + + if (!smooth) { + srcW++; srcH++; + } + + if (destW <= 0 || destH <= 0 || + srcW <= 0 || srcH <= 0 || + destX1 >= screenW || destY1 >= screenH || + srcX1 >= img.width || srcY1 >= img.height) { + return; + } + + int dx = (int) (srcW / (float) destW * PRECISIONF); + int dy = (int) (srcH / (float) destH * PRECISIONF); + + srcXOffset = (int) (destX1 < 0 ? -destX1 * dx : srcX1 * PRECISIONF); + srcYOffset = (int) (destY1 < 0 ? -destY1 * dy : srcY1 * PRECISIONF); + + if (destX1 < 0) { + destW += destX1; + destX1 = 0; + } + if (destY1 < 0) { + destH += destY1; + destY1 = 0; + } + + destW = low(destW, screenW - destX1); + destH = low(destH, screenH - destY1); + + int destOffset = destY1 * screenW + destX1; + srcBuffer = img.pixels; + + if (smooth) { + // use bilinear filtering + iw = img.width; + iw1 = img.width - 1; + ih1 = img.height - 1; + + switch (mode) { + + case BLEND: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + // davbol - renamed old blend_multiply to blend_blend + destPixels[destOffset + x] = + blend_blend(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case ADD: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_add_pin(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case SUBTRACT: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_sub_pin(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case LIGHTEST: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_lightest(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case DARKEST: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_darkest(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case REPLACE: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = filter_bilinear(); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case DIFFERENCE: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_difference(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case EXCLUSION: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_exclusion(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case MULTIPLY: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_multiply(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case SCREEN: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_screen(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case OVERLAY: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_overlay(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case HARD_LIGHT: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_hard_light(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case SOFT_LIGHT: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_soft_light(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + // davbol - proposed 2007-01-09 + case DODGE: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_dodge(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case BURN: + for (int y = 0; y < destH; y++) { + filter_new_scanline(); + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_burn(destPixels[destOffset + x], filter_bilinear()); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + } + + } else { + // nearest neighbour scaling (++fast!) + switch (mode) { + + case BLEND: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + // davbol - renamed old blend_multiply to blend_blend + destPixels[destOffset + x] = + blend_blend(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case ADD: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_add_pin(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case SUBTRACT: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_sub_pin(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case LIGHTEST: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_lightest(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case DARKEST: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_darkest(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case REPLACE: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = srcBuffer[sY + (sX >> PRECISIONB)]; + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case DIFFERENCE: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_difference(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case EXCLUSION: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_exclusion(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case MULTIPLY: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_multiply(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case SCREEN: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_screen(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case OVERLAY: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_overlay(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case HARD_LIGHT: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_hard_light(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case SOFT_LIGHT: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_soft_light(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + // davbol - proposed 2007-01-09 + case DODGE: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_dodge(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + case BURN: + for (int y = 0; y < destH; y++) { + sX = srcXOffset; + sY = (srcYOffset >> PRECISIONB) * img.width; + for (int x = 0; x < destW; x++) { + destPixels[destOffset + x] = + blend_burn(destPixels[destOffset + x], + srcBuffer[sY + (sX >> PRECISIONB)]); + sX += dx; + } + destOffset += screenW; + srcYOffset += dy; + } + break; + + } + } + } + + + private void filter_new_scanline() { + sX = srcXOffset; + fracV = srcYOffset & PREC_MAXVAL; + ifV = PREC_MAXVAL - fracV; + v1 = (srcYOffset >> PRECISIONB) * iw; + v2 = low((srcYOffset >> PRECISIONB) + 1, ih1) * iw; + } + + + private int filter_bilinear() { + fracU = sX & PREC_MAXVAL; + ifU = PREC_MAXVAL - fracU; + ul = (ifU * ifV) >> PRECISIONB; + ll = (ifU * fracV) >> PRECISIONB; + ur = (fracU * ifV) >> PRECISIONB; + lr = (fracU * fracV) >> PRECISIONB; + u1 = (sX >> PRECISIONB); + u2 = low(u1 + 1, iw1); + + // get color values of the 4 neighbouring texels + cUL = srcBuffer[v1 + u1]; + cUR = srcBuffer[v1 + u2]; + cLL = srcBuffer[v2 + u1]; + cLR = srcBuffer[v2 + u2]; + + r = ((ul*((cUL&RED_MASK)>>16) + ll*((cLL&RED_MASK)>>16) + + ur*((cUR&RED_MASK)>>16) + lr*((cLR&RED_MASK)>>16)) + << PREC_RED_SHIFT) & RED_MASK; + + g = ((ul*(cUL&GREEN_MASK) + ll*(cLL&GREEN_MASK) + + ur*(cUR&GREEN_MASK) + lr*(cLR&GREEN_MASK)) + >>> PRECISIONB) & GREEN_MASK; + + b = (ul*(cUL&BLUE_MASK) + ll*(cLL&BLUE_MASK) + + ur*(cUR&BLUE_MASK) + lr*(cLR&BLUE_MASK)) + >>> PRECISIONB; + + a = ((ul*((cUL&ALPHA_MASK)>>>24) + ll*((cLL&ALPHA_MASK)>>>24) + + ur*((cUR&ALPHA_MASK)>>>24) + lr*((cLR&ALPHA_MASK)>>>24)) + << PREC_ALPHA_SHIFT) & ALPHA_MASK; + + return a | r | g | b; + } + + + + ////////////////////////////////////////////////////////////// + + // internal blending methods + + + private static int low(int a, int b) { + return (a < b) ? a : b; + } + + + private static int high(int a, int b) { + return (a > b) ? a : b; + } + + // davbol - added peg helper, equiv to constrain(n,0,255) + private static int peg(int n) { + return (n < 0) ? 0 : ((n > 255) ? 255 : n); + } + + private static int mix(int a, int b, int f) { + return a + (((b - a) * f) >> 8); + } + + + + ///////////////////////////////////////////////////////////// + + // BLEND MODE IMPLEMENTIONS + + + private static int blend_blend(int a, int b) { + int f = (b & ALPHA_MASK) >>> 24; + + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + mix(a & RED_MASK, b & RED_MASK, f) & RED_MASK | + mix(a & GREEN_MASK, b & GREEN_MASK, f) & GREEN_MASK | + mix(a & BLUE_MASK, b & BLUE_MASK, f)); + } + + + /** + * additive blend with clipping + */ + private static int blend_add_pin(int a, int b) { + int f = (b & ALPHA_MASK) >>> 24; + + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + low(((a & RED_MASK) + + ((b & RED_MASK) >> 8) * f), RED_MASK) & RED_MASK | + low(((a & GREEN_MASK) + + ((b & GREEN_MASK) >> 8) * f), GREEN_MASK) & GREEN_MASK | + low((a & BLUE_MASK) + + (((b & BLUE_MASK) * f) >> 8), BLUE_MASK)); + } + + + /** + * subtractive blend with clipping + */ + private static int blend_sub_pin(int a, int b) { + int f = (b & ALPHA_MASK) >>> 24; + + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + high(((a & RED_MASK) - ((b & RED_MASK) >> 8) * f), + GREEN_MASK) & RED_MASK | + high(((a & GREEN_MASK) - ((b & GREEN_MASK) >> 8) * f), + BLUE_MASK) & GREEN_MASK | + high((a & BLUE_MASK) - (((b & BLUE_MASK) * f) >> 8), 0)); + } + + + /** + * only returns the blended lightest colour + */ + private static int blend_lightest(int a, int b) { + int f = (b & ALPHA_MASK) >>> 24; + + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + high(a & RED_MASK, ((b & RED_MASK) >> 8) * f) & RED_MASK | + high(a & GREEN_MASK, ((b & GREEN_MASK) >> 8) * f) & GREEN_MASK | + high(a & BLUE_MASK, ((b & BLUE_MASK) * f) >> 8)); + } + + + /** + * only returns the blended darkest colour + */ + private static int blend_darkest(int a, int b) { + int f = (b & ALPHA_MASK) >>> 24; + + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + mix(a & RED_MASK, + low(a & RED_MASK, + ((b & RED_MASK) >> 8) * f), f) & RED_MASK | + mix(a & GREEN_MASK, + low(a & GREEN_MASK, + ((b & GREEN_MASK) >> 8) * f), f) & GREEN_MASK | + mix(a & BLUE_MASK, + low(a & BLUE_MASK, + ((b & BLUE_MASK) * f) >> 8), f)); + } + + + /** + * returns the absolute value of the difference of the input colors + * C = |A - B| + */ + private static int blend_difference(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = (ar > br) ? (ar-br) : (br-ar); + int cg = (ag > bg) ? (ag-bg) : (bg-ag); + int cb = (ab > bb) ? (ab-bb) : (bb-ab); + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + /** + * Cousin of difference, algorithm used here is based on a Lingo version + * found here: http://www.mediamacros.com/item/item-1006687616/ + * (Not yet verified to be correct). + */ + private static int blend_exclusion(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = ar + br - ((ar * br) >> 7); + int cg = ag + bg - ((ag * bg) >> 7); + int cb = ab + bb - ((ab * bb) >> 7); + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + /** + * returns the product of the input colors + * C = A * B + */ + private static int blend_multiply(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = (ar * br) >> 8; + int cg = (ag * bg) >> 8; + int cb = (ab * bb) >> 8; + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + /** + * returns the inverse of the product of the inverses of the input colors + * (the inverse of multiply). C = 1 - (1-A) * (1-B) + */ + private static int blend_screen(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = 255 - (((255 - ar) * (255 - br)) >> 8); + int cg = 255 - (((255 - ag) * (255 - bg)) >> 8); + int cb = 255 - (((255 - ab) * (255 - bb)) >> 8); + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + /** + * returns either multiply or screen for darker or lighter values of A + * (the inverse of hard light) + * C = + * A < 0.5 : 2 * A * B + * A >=0.5 : 1 - (2 * (255-A) * (255-B)) + */ + private static int blend_overlay(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = (ar < 128) ? ((ar*br)>>7) : (255-(((255-ar)*(255-br))>>7)); + int cg = (ag < 128) ? ((ag*bg)>>7) : (255-(((255-ag)*(255-bg))>>7)); + int cb = (ab < 128) ? ((ab*bb)>>7) : (255-(((255-ab)*(255-bb))>>7)); + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + /** + * returns either multiply or screen for darker or lighter values of B + * (the inverse of overlay) + * C = + * B < 0.5 : 2 * A * B + * B >=0.5 : 1 - (2 * (255-A) * (255-B)) + */ + private static int blend_hard_light(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = (br < 128) ? ((ar*br)>>7) : (255-(((255-ar)*(255-br))>>7)); + int cg = (bg < 128) ? ((ag*bg)>>7) : (255-(((255-ag)*(255-bg))>>7)); + int cb = (bb < 128) ? ((ab*bb)>>7) : (255-(((255-ab)*(255-bb))>>7)); + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + /** + * returns the inverse multiply plus screen, which simplifies to + * C = 2AB + A^2 - 2A^2B + */ + private static int blend_soft_light(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = ((ar*br)>>7) + ((ar*ar)>>8) - ((ar*ar*br)>>15); + int cg = ((ag*bg)>>7) + ((ag*ag)>>8) - ((ag*ag*bg)>>15); + int cb = ((ab*bb)>>7) + ((ab*ab)>>8) - ((ab*ab*bb)>>15); + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + /** + * Returns the first (underlay) color divided by the inverse of + * the second (overlay) color. C = A / (255-B) + */ + private static int blend_dodge(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = (br==255) ? 255 : peg((ar << 8) / (255 - br)); // division requires pre-peg()-ing + int cg = (bg==255) ? 255 : peg((ag << 8) / (255 - bg)); // " + int cb = (bb==255) ? 255 : peg((ab << 8) / (255 - bb)); // " + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + /** + * returns the inverse of the inverse of the first (underlay) color + * divided by the second (overlay) color. C = 255 - (255-A) / B + */ + private static int blend_burn(int a, int b) { + // setup (this portion will always be the same) + int f = (b & ALPHA_MASK) >>> 24; + int ar = (a & RED_MASK) >> 16; + int ag = (a & GREEN_MASK) >> 8; + int ab = (a & BLUE_MASK); + int br = (b & RED_MASK) >> 16; + int bg = (b & GREEN_MASK) >> 8; + int bb = (b & BLUE_MASK); + // formula: + int cr = (br==0) ? 0 : 255 - peg(((255 - ar) << 8) / br); // division requires pre-peg()-ing + int cg = (bg==0) ? 0 : 255 - peg(((255 - ag) << 8) / bg); // " + int cb = (bb==0) ? 0 : 255 - peg(((255 - ab) << 8) / bb); // " + // alpha blend (this portion will always be the same) + return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | + (peg(ar + (((cr - ar) * f) >> 8)) << 16) | + (peg(ag + (((cg - ag) * f) >> 8)) << 8) | + (peg(ab + (((cb - ab) * f) >> 8)) ) ); + } + + + ////////////////////////////////////////////////////////////// + + // FILE I/O + + + static byte TIFF_HEADER[] = { + 77, 77, 0, 42, 0, 0, 0, 8, 0, 9, 0, -2, 0, 4, 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 3, 0, 0, 0, 1, + 0, 0, 0, 0, 1, 2, 0, 3, 0, 0, 0, 3, 0, 0, 0, 122, 1, 6, 0, 3, 0, + 0, 0, 1, 0, 2, 0, 0, 1, 17, 0, 4, 0, 0, 0, 1, 0, 0, 3, 0, 1, 21, + 0, 3, 0, 0, 0, 1, 0, 3, 0, 0, 1, 22, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, + 1, 23, 0, 4, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 8, 0, 8 + }; + + + static final String TIFF_ERROR = + "Error: Processing can only read its own TIFF files."; + + static protected PImage loadTIFF(byte tiff[]) { + if ((tiff[42] != tiff[102]) || // width/height in both places + (tiff[43] != tiff[103])) { + System.err.println(TIFF_ERROR); + return null; + } + + int width = + ((tiff[30] & 0xff) << 8) | (tiff[31] & 0xff); + int height = + ((tiff[42] & 0xff) << 8) | (tiff[43] & 0xff); + + int count = + ((tiff[114] & 0xff) << 24) | + ((tiff[115] & 0xff) << 16) | + ((tiff[116] & 0xff) << 8) | + (tiff[117] & 0xff); + if (count != width * height * 3) { + System.err.println(TIFF_ERROR + " (" + width + ", " + height +")"); + return null; + } + + // check the rest of the header + for (int i = 0; i < TIFF_HEADER.length; i++) { + if ((i == 30) || (i == 31) || (i == 42) || (i == 43) || + (i == 102) || (i == 103) || + (i == 114) || (i == 115) || (i == 116) || (i == 117)) continue; + + if (tiff[i] != TIFF_HEADER[i]) { + System.err.println(TIFF_ERROR + " (" + i + ")"); + return null; + } + } + + PImage outgoing = new PImage(width, height, RGB); + int index = 768; + count /= 3; + for (int i = 0; i < count; i++) { + outgoing.pixels[i] = + 0xFF000000 | + (tiff[index++] & 0xff) << 16 | + (tiff[index++] & 0xff) << 8 | + (tiff[index++] & 0xff); + } + return outgoing; + } + + + protected boolean saveTIFF(OutputStream output) { + // shutting off the warning, people can figure this out themselves + /* + if (format != RGB) { + System.err.println("Warning: only RGB information is saved with " + + ".tif files. Use .tga or .png for ARGB images and others."); + } + */ + try { + byte tiff[] = new byte[768]; + System.arraycopy(TIFF_HEADER, 0, tiff, 0, TIFF_HEADER.length); + + tiff[30] = (byte) ((width >> 8) & 0xff); + tiff[31] = (byte) ((width) & 0xff); + tiff[42] = tiff[102] = (byte) ((height >> 8) & 0xff); + tiff[43] = tiff[103] = (byte) ((height) & 0xff); + + int count = width*height*3; + tiff[114] = (byte) ((count >> 24) & 0xff); + tiff[115] = (byte) ((count >> 16) & 0xff); + tiff[116] = (byte) ((count >> 8) & 0xff); + tiff[117] = (byte) ((count) & 0xff); + + // spew the header to the disk + output.write(tiff); + + for (int i = 0; i < pixels.length; i++) { + output.write((pixels[i] >> 16) & 0xff); + output.write((pixels[i] >> 8) & 0xff); + output.write(pixels[i] & 0xff); + } + output.flush(); + return true; + + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } + + + /** + * Creates a Targa32 formatted byte sequence of specified + * pixel buffer using RLE compression. + *

    + * Also figured out how to avoid parsing the image upside-down + * (there's a header flag to set the image origin to top-left) + *

    + * Starting with revision 0092, the format setting is taken into account: + *
      + *
    • ALPHA images written as 8bit grayscale (uses lowest byte) + *
    • RGB → 24 bits + *
    • ARGB → 32 bits + *
    + * All versions are RLE compressed. + *

    + * Contributed by toxi 8-10 May 2005, based on this RLE + * specification + */ + protected boolean saveTGA(OutputStream output) { + byte header[] = new byte[18]; + + if (format == ALPHA) { // save ALPHA images as 8bit grayscale + header[2] = 0x0B; + header[16] = 0x08; + header[17] = 0x28; + + } else if (format == RGB) { + header[2] = 0x0A; + header[16] = 24; + header[17] = 0x20; + + } else if (format == ARGB) { + header[2] = 0x0A; + header[16] = 32; + header[17] = 0x28; + + } else { + throw new RuntimeException("Image format not recognized inside save()"); + } + // set image dimensions lo-hi byte order + header[12] = (byte) (width & 0xff); + header[13] = (byte) (width >> 8); + header[14] = (byte) (height & 0xff); + header[15] = (byte) (height >> 8); + + try { + output.write(header); + + int maxLen = height * width; + int index = 0; + int col; //, prevCol; + int[] currChunk = new int[128]; + + // 8bit image exporter is in separate loop + // to avoid excessive conditionals... + if (format == ALPHA) { + while (index < maxLen) { + boolean isRLE = false; + int rle = 1; + currChunk[0] = col = pixels[index] & 0xff; + while (index + rle < maxLen) { + if (col != (pixels[index + rle]&0xff) || rle == 128) { + isRLE = (rle > 1); + break; + } + rle++; + } + if (isRLE) { + output.write(0x80 | (rle - 1)); + output.write(col); + + } else { + rle = 1; + while (index + rle < maxLen) { + int cscan = pixels[index + rle] & 0xff; + if ((col != cscan && rle < 128) || rle < 3) { + currChunk[rle] = col = cscan; + } else { + if (col == cscan) rle -= 2; + break; + } + rle++; + } + output.write(rle - 1); + for (int i = 0; i < rle; i++) output.write(currChunk[i]); + } + index += rle; + } + } else { // export 24/32 bit TARGA + while (index < maxLen) { + boolean isRLE = false; + currChunk[0] = col = pixels[index]; + int rle = 1; + // try to find repeating bytes (min. len = 2 pixels) + // maximum chunk size is 128 pixels + while (index + rle < maxLen) { + if (col != pixels[index + rle] || rle == 128) { + isRLE = (rle > 1); // set flag for RLE chunk + break; + } + rle++; + } + if (isRLE) { + output.write(128 | (rle - 1)); + output.write(col & 0xff); + output.write(col >> 8 & 0xff); + output.write(col >> 16 & 0xff); + if (format == ARGB) output.write(col >>> 24 & 0xff); + + } else { // not RLE + rle = 1; + while (index + rle < maxLen) { + if ((col != pixels[index + rle] && rle < 128) || rle < 3) { + currChunk[rle] = col = pixels[index + rle]; + } else { + // check if the exit condition was the start of + // a repeating colour + if (col == pixels[index + rle]) rle -= 2; + break; + } + rle++; + } + // write uncompressed chunk + output.write(rle - 1); + if (format == ARGB) { + for (int i = 0; i < rle; i++) { + col = currChunk[i]; + output.write(col & 0xff); + output.write(col >> 8 & 0xff); + output.write(col >> 16 & 0xff); + output.write(col >>> 24 & 0xff); + } + } else { + for (int i = 0; i < rle; i++) { + col = currChunk[i]; + output.write(col & 0xff); + output.write(col >> 8 & 0xff); + output.write(col >> 16 & 0xff); + } + } + } + index += rle; + } + } + output.flush(); + return true; + + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + + /** + * Use ImageIO functions from Java 1.4 and later to handle image save. + * Various formats are supported, typically jpeg, png, bmp, and wbmp. + * To get a list of the supported formats for writing, use:
    + * println(javax.imageio.ImageIO.getReaderFormatNames()) + */ + protected void saveImageIO(String path) throws IOException { + try { + BufferedImage bimage = + new BufferedImage(width, height, (format == ARGB) ? + BufferedImage.TYPE_INT_ARGB : + BufferedImage.TYPE_INT_RGB); + /* + Class bufferedImageClass = + Class.forName("java.awt.image.BufferedImage"); + Constructor bufferedImageConstructor = + bufferedImageClass.getConstructor(new Class[] { + Integer.TYPE, + Integer.TYPE, + Integer.TYPE }); + Field typeIntRgbField = bufferedImageClass.getField("TYPE_INT_RGB"); + int typeIntRgb = typeIntRgbField.getInt(typeIntRgbField); + Field typeIntArgbField = bufferedImageClass.getField("TYPE_INT_ARGB"); + int typeIntArgb = typeIntArgbField.getInt(typeIntArgbField); + Object bimage = + bufferedImageConstructor.newInstance(new Object[] { + new Integer(width), + new Integer(height), + new Integer((format == ARGB) ? typeIntArgb : typeIntRgb) + }); + */ + + bimage.setRGB(0, 0, width, height, pixels, 0, width); + /* + Method setRgbMethod = + bufferedImageClass.getMethod("setRGB", new Class[] { + Integer.TYPE, Integer.TYPE, + Integer.TYPE, Integer.TYPE, + pixels.getClass(), + Integer.TYPE, Integer.TYPE + }); + setRgbMethod.invoke(bimage, new Object[] { + new Integer(0), new Integer(0), + new Integer(width), new Integer(height), + pixels, new Integer(0), new Integer(width) + }); + */ + + File file = new File(path); + String extension = path.substring(path.lastIndexOf('.') + 1); + + ImageIO.write(bimage, extension, file); + /* + Class renderedImageClass = + Class.forName("java.awt.image.RenderedImage"); + Class ioClass = Class.forName("javax.imageio.ImageIO"); + Method writeMethod = + ioClass.getMethod("write", new Class[] { + renderedImageClass, String.class, File.class + }); + writeMethod.invoke(null, new Object[] { bimage, extension, file }); + */ + + } catch (Exception e) { + e.printStackTrace(); + throw new IOException("image save failed."); + } + } + + + protected String[] saveImageFormats; + + /** + * Save this image to disk. + *

    + * As of revision 0100, this function requires an absolute path, + * in order to avoid confusion. To save inside the sketch folder, + * use the function savePath() from PApplet, or use saveFrame() instead. + * As of revision 0116, savePath() is not needed if this object has been + * created (as recommended) via createImage() or createGraphics() or + * one of its neighbors. + *

    + * As of revision 0115, when using Java 1.4 and later, you can write + * to several formats besides tga and tiff. If Java 1.4 is installed + * and the extension used is supported (usually png, jpg, jpeg, bmp, + * and tiff), then those methods will be used to write the image. + * To get a list of the supported formats for writing, use:
    + * println(javax.imageio.ImageIO.getReaderFormatNames()) + *

    + * To use the original built-in image writers, use .tga or .tif as the + * extension, or don't include an extension. When no extension is used, + * the extension .tif will be added to the file name. + *

    + * The ImageIO API claims to support wbmp files, however they probably + * require a black and white image. Basic testing produced a zero-length + * file with no error. + */ + public void save(String path) { // ignore + boolean success = false; + + File file = new File(path); + if (!file.isAbsolute()) { + if (parent != null) { + //file = new File(parent.savePath(filename)); + path = parent.savePath(path); + } else { + String msg = "PImage.save() requires an absolute path. " + + "Use createImage(), or pass savePath() to save()."; + PGraphics.showException(msg); + } + } + + // Make sure the pixel data is ready to go + loadPixels(); + + try { + OutputStream os = null; + + if (saveImageFormats == null) { + saveImageFormats = javax.imageio.ImageIO.getWriterFormatNames(); + } + if (saveImageFormats != null) { + for (int i = 0; i < saveImageFormats.length; i++) { + if (path.endsWith("." + saveImageFormats[i])) { + saveImageIO(path); + return; + } + } + } + + if (path.toLowerCase().endsWith(".tga")) { + os = new BufferedOutputStream(new FileOutputStream(path), 32768); + success = saveTGA(os); //, pixels, width, height, format); + + } else { + if (!path.toLowerCase().endsWith(".tif") && + !path.toLowerCase().endsWith(".tiff")) { + // if no .tif extension, add it.. + path += ".tif"; + } + os = new BufferedOutputStream(new FileOutputStream(path), 32768); + success = saveTIFF(os); //, pixels, width, height); + } + os.flush(); + os.close(); + + } catch (IOException e) { + //System.err.println("Error while saving image."); + e.printStackTrace(); + success = false; + } + if (!success) { + throw new RuntimeException("Error while saving image."); + } + } +} + diff --git a/core/preproc/preproc.jar b/core/preproc/preproc.jar new file mode 100644 index 000000000..e025d6a25 Binary files /dev/null and b/core/preproc/preproc.jar differ diff --git a/core/preproc/src/processing/build/PAppletMethods.java b/core/preproc/src/processing/build/PAppletMethods.java new file mode 100644 index 000000000..ed27b2f8c --- /dev/null +++ b/core/preproc/src/processing/build/PAppletMethods.java @@ -0,0 +1,236 @@ +package processing.build; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + + +/** + * Ant Task for copying the PImage and PGraphics methods into PApplet. + */ +public class PAppletMethods extends Task { + + private File baseDir; + + + public PAppletMethods() { + } + + + public void setDir(String dir) { + baseDir = new File(dir); + } + + + public void execute() throws BuildException { + // Do a bunch of checks... + if (baseDir == null) { + throw new BuildException("dir parameter must be set!"); + } + + File graphicsFile = new File(baseDir, "PGraphics.java"); + File appletFile = new File(baseDir, "PApplet.java"); + File imageFile = new File(baseDir, "PImage.java"); + + if (!graphicsFile.exists() || !graphicsFile.canRead()) { + throw new BuildException("PGraphics file not readable: " + + graphicsFile.getAbsolutePath()); + } + + if (!appletFile.exists() || !appletFile.canRead() || !appletFile.canWrite()) { + throw new BuildException("PApplet file not read/writeable: " + + appletFile.getAbsolutePath()); + } + + if (!imageFile.exists() || !imageFile.canRead()) { + throw new BuildException("PImage file not readable: " + + imageFile.getAbsolutePath()); + } + + // Looking good, let's do this! + ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(outBytes); + StringBuffer content = new StringBuffer(); + + try{ + BufferedReader applet = createReader(appletFile); + String line; + while ((line = applet.readLine()) != null) { + out.println(line); + content.append(line + "\n"); + + if (line.indexOf("public functions for processing.core") >= 0) { + break; + } + } + + // read the rest of the file and append it to the + while ((line = applet.readLine()) != null) { + content.append(line + "\n"); + } + + applet.close(); + process(out, graphicsFile); + process(out, imageFile); + + out.println("}"); + + } catch (IOException e) { + e.printStackTrace(); + + } catch(Exception ex) { + ex.printStackTrace(); + } + out.flush(); + + if (content.toString().equals(outBytes.toString())) { + System.out.println("No changes to PApplet API."); + } else { + System.out.println("Updating PApplet with API changes from PImage or PGraphics."); + try { + PrintStream temp = new PrintStream(appletFile); + temp.print(outBytes.toString()); + temp.flush(); + temp.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + } + + + private void process(PrintStream out, File input) throws IOException { + BufferedReader in = createReader(input); + int comments = 0; + String line = null; + + while ((line = in.readLine()) != null) { + String decl = ""; + + // Keep track of comments + if (line.matches(Pattern.quote("/*"))) { + comments ++; + } + + if (line.matches(Pattern.quote("*/"))) { + comments --; + } + + // Ignore everything inside comments + if (comments > 0) { + continue; + } + + boolean gotSomething = false; + boolean gotStatic = false; + + Matcher result; + + if ((result = Pattern.compile("^\\s*public ([\\w\\[\\]]+) [a-zA-z_]+\\(.*$").matcher(line)).matches()) { + gotSomething = true; + } + else if ((result = Pattern.compile("^\\s*abstract public ([\\w\\[\\]]+) [a-zA-z_]+\\(.*$").matcher(line)).matches()) { + gotSomething = true; + } + else if ((result = Pattern.compile("^\\s*public final ([\\w\\[\\]]+) [a-zA-z_]+\\(.*$").matcher(line)).matches()) { + gotSomething = true; + } + else if ((result = Pattern.compile("^\\s*static public ([\\w\\[\\]]+) [a-zA-z_]+\\(.*$").matcher(line)).matches()) { + gotSomething = true; + gotStatic = true; + } + + // if function is marked "// ignore" then, uh, ignore it. + if (gotSomething && line.indexOf("// ignore") >= 0) { + gotSomething = false; + } + + String returns = ""; + if (gotSomething) { + if (result.group(1).equals("void")) { + returns = ""; + } else { + returns = "return "; + } + + // remove the abstract modifier + line = line.replaceFirst(Pattern.quote("abstract"), " "); + + // replace semicolons with a start def + line = line.replaceAll(Pattern.quote(";"), " {\n"); + + out.println("\n\n" + line); + + decl += line; + while(line.indexOf(')') == -1) { + line = in.readLine(); + decl += line; + line = line.replaceAll("\\;\\s*$", " {\n"); + out.println(line); + } + + result = Pattern.compile(".*?\\s(\\S+)\\(.*?").matcher(decl); + result.matches(); // try to match. DON't remove this or things will stop working! + String declName = result.group(1); + String gline = ""; + String rline = ""; + if (gotStatic) { + gline = " " + returns + "PGraphics." + declName + "("; + } else { + rline = " if (recorder != null) recorder." + declName + "("; + gline = " " + returns + "g." + declName + "("; + } + + decl = decl.replaceAll("\\s+", " "); // smush onto a single line + decl = decl.replaceFirst("^.*\\(", ""); + decl = decl.replaceFirst("\\).*$", ""); + + int prev = 0; + String parts[] = decl.split("\\, "); + + for (String part : parts) { + if (!part.trim().equals("")) { + String blargh[] = part.split(" "); + String theArg = blargh[1].replaceAll("[\\[\\]]", ""); + + if (prev != 0) { + gline += ", "; + rline += ", "; + } + + gline += theArg; + rline += theArg; + prev = 1; + } + } + + gline += ");"; + rline += ");"; + + if (!gotStatic && returns.equals("")) { + out.println(rline); + } + + out.println(gline); + out.println(" }"); + } + } + + in.close(); + } + + + private static BufferedReader createReader(File f) throws FileNotFoundException { + return new BufferedReader(new InputStreamReader(new FileInputStream(f))); + } +} diff --git a/core/src/processing/core/PApplet.java b/core/src/processing/core/PApplet.java index ecbd40d98..06541c547 100644 --- a/core/src/processing/core/PApplet.java +++ b/core/src/processing/core/PApplet.java @@ -2687,6 +2687,12 @@ public class PApplet extends Applet return (a > b) ? a : b; } + /* + static public final double max(double a, double b) { + return (a > b) ? a : b; + } + */ + static public final int max(int a, int b, int c) { return (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c); @@ -2732,6 +2738,26 @@ public class PApplet extends Applet } + /** + * Find the maximum value in an array. + * Throws an ArrayIndexOutOfBoundsException if the array is length 0. + * @param list the source array + * @return The maximum value + */ + /* + static public final double max(double[] list) { + if (list.length == 0) { + throw new ArrayIndexOutOfBoundsException(ERROR_MIN_MAX); + } + double max = list[0]; + for (int i = 1; i < list.length; i++) { + if (list[i] > max) max = list[i]; + } + return max; + } + */ + + static public final int min(int a, int b) { return (a < b) ? a : b; } @@ -2740,6 +2766,12 @@ public class PApplet extends Applet return (a < b) ? a : b; } + /* + static public final double min(double a, double b) { + return (a < b) ? a : b; + } + */ + static public final int min(int a, int b, int c) { return (a < b) ? ((a < c) ? a : c) : ((b < c) ? b : c); @@ -2749,6 +2781,12 @@ public class PApplet extends Applet return (a < b) ? ((a < c) ? a : c) : ((b < c) ? b : c); } + /* + static public final double min(double a, double b, double c) { + return (a < b) ? ((a < c) ? a : c) : ((b < c) ? b : c); + } + */ + /** * Find the minimum value in an array. @@ -2766,6 +2804,8 @@ public class PApplet extends Applet } return min; } + + /** * Find the minimum value in an array. * Throws an ArrayIndexOutOfBoundsException if the array is length 0. @@ -2784,6 +2824,25 @@ public class PApplet extends Applet } + /** + * Find the minimum value in an array. + * Throws an ArrayIndexOutOfBoundsException if the array is length 0. + * @param list the source array + * @return The minimum value + */ + /* + static public final double min(double[] list) { + if (list.length == 0) { + throw new ArrayIndexOutOfBoundsException(ERROR_MIN_MAX); + } + double min = list[0]; + for (int i = 1; i < list.length; i++) { + if (list[i] < min) min = list[i]; + } + return min; + } + */ + static public final int constrain(int amt, int low, int high) { return (amt < low) ? low : ((amt > high) ? high : amt); } @@ -2888,11 +2947,13 @@ public class PApplet extends Applet } + /* static public final double map(double value, double istart, double istop, double ostart, double ostop) { return ostart + (ostop - ostart) * ((value - istart) / (istop - istart)); } + */ @@ -4355,18 +4416,34 @@ public class PApplet extends Applet * Saves bytes to a specific File location specified by the user. */ static public void saveBytes(File file, byte buffer[]) { + File tempFile = null; try { + File parentDir = file.getParentFile(); + tempFile = File.createTempFile(file.getName(), null, parentDir); + + /* String filename = file.getAbsolutePath(); createPath(filename); OutputStream output = new FileOutputStream(file); if (file.getName().toLowerCase().endsWith(".gz")) { output = new GZIPOutputStream(output); } + */ + OutputStream output = createOutput(tempFile); saveBytes(output, buffer); output.close(); + output = null; + + if (!tempFile.renameTo(file)) { + System.err.println("Could not rename temporary file " + + tempFile.getAbsolutePath()); + } } catch (IOException e) { System.err.println("error saving bytes to " + file); + if (tempFile != null) { + tempFile.delete(); + } e.printStackTrace(); } } @@ -4393,6 +4470,8 @@ public class PApplet extends Applet static public void saveStrings(File file, String strings[]) { + saveStrings(createOutput(file), strings); + /* try { String location = file.getAbsolutePath(); createPath(location); @@ -4406,18 +4485,17 @@ public class PApplet extends Applet } catch (IOException e) { e.printStackTrace(); } + */ } static public void saveStrings(OutputStream output, String strings[]) { - try { - OutputStreamWriter osw = new OutputStreamWriter(output, "UTF-8"); - PrintWriter writer = new PrintWriter(osw); + PrintWriter writer = createWriter(output); for (int i = 0; i < strings.length; i++) { writer.println(strings[i]); } writer.flush(); - } catch (UnsupportedEncodingException e) { } // will not happen + writer.close(); } diff --git a/core/src/processing/core/PGraphics.java b/core/src/processing/core/PGraphics.java index cc5562bcd..346b11cd1 100644 --- a/core/src/processing/core/PGraphics.java +++ b/core/src/processing/core/PGraphics.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2004-08 Ben Fry and Casey Reas + Copyright (c) 2004-09 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or diff --git a/core/src/processing/core/PGraphics3D.java b/core/src/processing/core/PGraphics3D.java index ba5af3c15..57dde4343 100644 --- a/core/src/processing/core/PGraphics3D.java +++ b/core/src/processing/core/PGraphics3D.java @@ -358,12 +358,12 @@ public class PGraphics3D extends PGraphics { cameraInv = new PMatrix3D(); // set up the default camera - camera(); +// camera(); // defaults to perspective, if the user has setup up their // own projection, they'll need to fix it after resize anyway. // this helps the people who haven't set up their own projection. - perspective(); +// perspective(); } @@ -478,6 +478,12 @@ public class PGraphics3D extends PGraphics { forwardTransform = modelview; reverseTransform = modelviewInv; + // set up the default camera + camera(); + + // defaults to perspective, if the user has setup up their + // own projection, they'll need to fix it after resize anyway. + // this helps the people who haven't set up their own projection. perspective(); // easiest for beginners @@ -1344,7 +1350,7 @@ public class PGraphics3D extends PGraphics { boolean bClipped = false; int clippedCount = 0; - cameraNear = -8; +// cameraNear = -8; if (vertices[a][VZ] > cameraNear) { aClipped = true; clippedCount++; @@ -1358,8 +1364,15 @@ public class PGraphics3D extends PGraphics { clippedCount++; } if (clippedCount == 0) { +// if (vertices[a][VZ] < cameraFar && +// vertices[b][VZ] < cameraFar && +// vertices[c][VZ] < cameraFar) { addTriangleWithoutClip(a, b, c); +// } +// } else if (true) { +// return; + } else if (clippedCount == 3) { // In this case there is only one visible point. |/| // So we'll have to make two new points on the clip line <| | @@ -3502,6 +3515,7 @@ public class PGraphics3D extends PGraphics { 0, y, 0, ty, 0, 0, z, tz, 0, 0, 0, 1); + updateProjection(); frustumMode = false; } @@ -3569,6 +3583,12 @@ public class PGraphics3D extends PGraphics { 0, (2*znear)/(top-bottom), (top+bottom)/(top-bottom), 0, 0, 0, -(zfar+znear)/(zfar-znear),-(2*zfar*znear)/(zfar-znear), 0, 0, -1, 0); + updateProjection(); + } + + + /** Called after the 'projection' PMatrix3D has changed. */ + protected void updateProjection() { } diff --git a/core/src/processing/core/PImage.java b/core/src/processing/core/PImage.java index 9dd126e4b..c1b38f095 100644 --- a/core/src/processing/core/PImage.java +++ b/core/src/processing/core/PImage.java @@ -182,6 +182,7 @@ public class PImage implements PConstants, Cloneable { raster.getDataElements(0, 0, width, height, pixels); } else { // go the old school java 1.0 route +// System.out.println(img.getClass().getName()); width = img.getWidth(null); height = img.getHeight(null); pixels = new int[width * height]; @@ -690,7 +691,7 @@ public class PImage implements PConstants, Cloneable { throw new RuntimeException("Use filter(POSTERIZE, int levels) " + "instead of filter(POSTERIZE)"); - case RGB: + case OPAQUE: for (int i = 0; i < pixels.length; i++) { pixels[i] |= 0xff000000; } diff --git a/core/src/processing/core/PVector.java b/core/src/processing/core/PVector.java index 78209bb60..91be2f52b 100644 --- a/core/src/processing/core/PVector.java +++ b/core/src/processing/core/PVector.java @@ -430,6 +430,11 @@ public class PVector { } + static public float dot(PVector v1, PVector v2) { + return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; + } + + /** * Return a vector composed of the cross product between this and another. */ diff --git a/core/todo.txt b/core/todo.txt index 988df2d09..cf4488272 100644 --- a/core/todo.txt +++ b/core/todo.txt @@ -1,8 +1,30 @@ -0169 core -X remove major try/catch block from PApplet.main() -X hopefully will allow some exception stuff to come through properly -X PVector.angleDistance() returns NaN -X http://dev.processing.org/bugs/show_bug.cgi?id=1316 +0171 core +X Blurred PImages in OPENGL sketches +X removed NPOT texture support (for further testing) +X http://dev.processing.org/bugs/show_bug.cgi?id=1352 + +_ open up the pdf library more (philho) +_ http://dev.processing.org/bugs/show_bug.cgi?id=1343 +_ changing vertex alpha in P3D in a QUAD_STRIP is ignored +_ with smoothing, it works fine, but with PTriangle, it's not +_ smooth() not working with applets an createGraphics(JAVA2D) +_ but works fine with applications +_ get() with OPENGL is grabbing the wrong coords +_ http://dev.processing.org/bugs/show_bug.cgi?id=1349 + +_ No textures render with hint(ENABLE_ACCURATE_TEXTURES) +_ http://dev.processing.org/bugs/show_bug.cgi?id=985 +_ need to remove the hint from the reference +_ need to throw an error when it's used +_ deal with issue of single pixel seam at the edge of textures +_ http://dev.processing.org/bugs/show_bug.cgi?id=602 +_ should vertexTexture() divide by width/height or width-1/height-1? + +_ key and mouse events delivered out of order +_ http://dev.processing.org/bugs/show_bug.cgi?id=638 +_ key/mouse events have concurrency problems with noLoop() +_ http://dev.processing.org/bugs/show_bug.cgi?id=1323 +_ need to say "no drawing inside mouse/key events w/ noLoop" _ make the index lookup use numbers up to 256? @@ -170,12 +192,6 @@ _ smooth in P3D has zbuffer glitches _ http://dev.processing.org/bugs/show_bug.cgi?id=1000 _ smoothing is slow _ http://dev.processing.org/bugs/show_bug.cgi?id=1001 -_ No textures render with hint(ENABLE_ACCURATE_TEXTURES) -_ http://dev.processing.org/bugs/show_bug.cgi?id=985 -_ need to remove the hint from the reference -_ need to throw an error when it's used -_ deal with issue of single pixel seam at the edge of textures -_ ??? what is the bug # for this one? _ textured sphere example needs to set normals _ also needs fix for last edge and the seam @@ -235,8 +251,6 @@ _ PApplet.main(new String[] { "classname" }) won't pass in args _ this means that no args are after passed to the class _ the fix would be to use the following as the call to main() _ PApplet.main(append(new String[] { "classname }, args)); -_ key and mouse events delivered out of order -_ http://dev.processing.org/bugs/show_bug.cgi?id=638 _ figure out why 1024x768 image takes 3.5 seconds to load _ would using a BufferedImage work better? _ is the image actually a BufferedImage so PixelGrabber is a waste?