From 2e26a2d994d6a082f4a83ddc7bcdb7f29d9b69b6 Mon Sep 17 00:00:00 2001 From: "David A. Mellis" Date: Thu, 22 Oct 2009 00:56:16 +0000 Subject: [PATCH] Syncing with Processing 1.0.9 (revision 5766). --- app/src/processing/app/Base.java | 153 +- app/src/processing/app/Editor.java | 65 + app/src/processing/app/Preferences.java | 174 +- app/src/processing/app/Sketch.java | 36 +- app/src/processing/app/debug/Runner.java | 6 + app/src/processing/app/macosx/Platform.java | 2 + .../app/preproc/PdePreprocessor.java | 425 +- .../dist/Arduino.app/Contents/Info.plist | 30 +- build/macosx/make.sh | 6 - build/shared/lib/preferences.txt | 1 - build/shared/libraries/howto.txt | 5 - .../libraries/javascript/library/export.txt | 6 - .../javascript/library/javascript.jar | Bin 2568 -> 0 bytes .../shared/libraries/minim/library/jl1.0.jar | Bin 105446 -> 0 bytes .../libraries/minim/library/jsminim.jar | Bin 46443 -> 0 bytes .../libraries/minim/library/minim-spi.jar | Bin 2858 -> 0 bytes .../shared/libraries/minim/library/minim.jar | Bin 51439 -> 0 bytes .../libraries/minim/library/mp3spi1.9.4.jar | Bin 24538 -> 0 bytes .../libraries/minim/library/tritonus_aos.jar | Bin 10401 -> 0 bytes .../minim/library/tritonus_share.jar | Bin 102673 -> 0 bytes build/shared/libraries/minim/license.txt | 339 - build/shared/libraries/minim/version.txt | 1 - build/shared/revisions.txt | 89 + core/done.txt | 26 + core/preproc/.classpath | 7 + core/preproc/.project | 17 + core/preproc/build.xml | 22 + core/preproc/demo/PApplet.java | 8155 +++++++++++++++++ core/preproc/demo/PGraphics.java | 5043 ++++++++++ core/preproc/demo/PImage.java | 2713 ++++++ core/preproc/preproc.jar | Bin 0 -> 3943 bytes .../src/processing/build/PAppletMethods.java | 236 + core/src/processing/core/PApplet.java | 86 +- core/src/processing/core/PGraphics.java | 2 +- core/src/processing/core/PGraphics3D.java | 26 +- core/src/processing/core/PImage.java | 3 +- core/src/processing/core/PVector.java | 5 + core/todo.txt | 40 +- 38 files changed, 16818 insertions(+), 901 deletions(-) delete mode 100644 build/shared/libraries/howto.txt delete mode 100644 build/shared/libraries/javascript/library/export.txt delete mode 100644 build/shared/libraries/javascript/library/javascript.jar delete mode 100755 build/shared/libraries/minim/library/jl1.0.jar delete mode 100755 build/shared/libraries/minim/library/jsminim.jar delete mode 100755 build/shared/libraries/minim/library/minim-spi.jar delete mode 100755 build/shared/libraries/minim/library/minim.jar delete mode 100755 build/shared/libraries/minim/library/mp3spi1.9.4.jar delete mode 100755 build/shared/libraries/minim/library/tritonus_aos.jar delete mode 100755 build/shared/libraries/minim/library/tritonus_share.jar delete mode 100755 build/shared/libraries/minim/license.txt delete mode 100755 build/shared/libraries/minim/version.txt create mode 100644 core/preproc/.classpath create mode 100644 core/preproc/.project create mode 100644 core/preproc/build.xml create mode 100644 core/preproc/demo/PApplet.java create mode 100644 core/preproc/demo/PGraphics.java create mode 100644 core/preproc/demo/PImage.java create mode 100644 core/preproc/preproc.jar create mode 100644 core/preproc/src/processing/build/PAppletMethods.java 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 93c7b9251692ab868f7543d47a7b4d2fe5145287..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2568 zcmaKuc{r47AIG1ujbW0EwXz>$nV2!Q7%D^=qsC5{j5!lC)*_*BY-6pH=!il|CQ>t& zYLaC_6xos^WNp(cSsHtIIp@4@yl;A*>wd2LpWo;HJ=gX7-q#0j$;~4GtUt6c1I@oS z|9l{T7~n?^3M3H&$VhuT2*B+l?r3~^ZP|zL0At)BF8qIMz5l0HbYm@*7)%T#ouUK; z{f#mB0Y-G~Q2w`m)Oe%01RFLP}%~6wKWSNkpqG#{(c%HA7WtOU+oZPFGNTPpAJIK-A{k!bJGag)Z>JlAw2N&~WuHhQexD?f=2dwwh;@JJC zU>F1Fp|tTZ!%Ern`F%5X2C9t&0qFhFOjacV#mr42cy^WMASr?wO&GK5t8$FOl4Dc2 zyZM*PxMoIim#LSFD>6J^h8vsPCI#(oDqK8irkc_9_3AILuwb3#8@LsctDFIMx4^GOE?@;kdv4n0Vm@41!L)x9cV8>Yl~ zf4Dp>yfjKmZDQth($|pp!I{hb1G6c`j5F%gh9=z zlOsm(h+I}(y>bG^>Kkav50?9jpVm}-{WyG|*~3>mk!efXds^7c9Js6|D^A(NX{P|= z+Nn2BNhX3NkkaTHzE2p3nn`-}KmlyBgT>KxqeGnUTjoWL`|DL7c^h1ye*rXakWX0Z zy|kEiQdP$-1M-LwQ5dC8sk~sXif`=oJ#S0}C&1ud#f*;l{8aWJ4t0pqW1v*SBUdjl|6+V+ow@EQXx6)T7wREd}RqjsY3iv{g5?cfI8BHb0p6T9C z@4F=53t?I)>K9D=C#sg;73{4S^R2+u6?Hh7a&D{dA?`xFmY+Z=a3bT}nqGSPyqJ+# z58n60Jkdjsd#xp=o(BLFwoJ@X4=R}y^pm*6r4JtM#EUl8NMM?<){;VIdf+_#Y)rI5 zq6v=Q1C74OGBZ7=_Wr~l=?^HF*J+wrqcI)xL&=d*j72VMScinnx0@;Sn}O_|J#|JO zBf?teBHG+~2P3PO0aRk9MglZDkVc+ABNfYgHb(1k^66|$HPJ=Jw9RJW$s)XaInm13 zg|ycZy{Foy@7PB_GZ(!=!)n7C2SSLG>s`Ee&%H+mHU2!?dEB(D1kSnKY~n5NYIs!mL|ek?y>(G^fVGB2c?X=U8vcSbPJmsB~hIon#Es z^fLonv(FwyN3N_aZX4n~BKZD&Q3#LGLC2Bhdu@}D8j42Ax6L8@$SS_e z+p&C$WtWbr&^(HdgrH6#<4MC~zQjG-?%B_Wr`_1WZ#1`nV>RR~VOz@|KetNtEcob` zl_Er8Le0g|;-%s~g8G6&rlnmg^~}NP2cf~F*%Zf`ufe4a4v)HKzl`|8Ig-8V6FKcw zXQ|S77?{Eh6$6LbNgEz)F$XR2W$7k)Nrw24+Pr?B#gL@A@&X_7f#J2d2gGmLM((?yW>(+doPn46u#Yw;QyA_iwGR4*`IUjQ4-Np>Q88QKexMm^{B_Ov5r6YFypeyOd&4hpXcud{`BMG~ id~-}T!cXyS1>YE&O=#Y=5&+;`d+)3z@6ft;0sjUTat3h# 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 17f7c0aa72f53c8a949bd7a5d899f86aa4227f2f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 105446 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

+`ms8KJfj(EMV}#=T2;0}_JdMMf;{~YAK!Lm5 zi|bvxp88K@fd3CeRhUl}{ht8)T^tTYbjglVo4;ZHp|j;^QOH+^edER<&oIN{h~gy`3vg*E>ycJ;OfGpUjpLo{#(HH oZNk-+5x|_LzS?iAHmP(fNmcq^pYEI! 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 J=<1)p{s)=hEbRaQ 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 4760de485666d1971601903ac03dc3c9632ec963..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2858 zcmb7G2{@Gd8Xp`pQ_->An8ZYua2ZR5C}Ww)n4!s_Y#r0gWSnE{OSZ_8jvVWB7=}}} ztX*3~O7>_(nskI~EJIPs)k29gQ=MxF_qp%)e9!ZJ&-XmP|M&jh_x=4|ELz|P2>_UL zZvjjd2f1kOgo61m(30L)u59$?Vw8jpn0>Z%VJJyq74 zOdS?`jLi?gqCw!waQ<0o4itoQWB+@=;pnvo$aB6DiDzLHHxD-o%-72eX6#RN^Ry-r zJbj369NlDV2|lR4r*R?yS4z%UR+=w5->PqVZff2ch44mZI(JWJ6iPWHS;eaEENTE46rx`q zlFFH?NW9SD{4!|InUQhhs9Ype_q8d8EZ-tJtiC6lkIVCL3v^tH4*+Q6+?(?Z-I!;b zpAQL7`ARb7IBI({T%;7myfyQL%@^z~%j%4Z6cG_R*Q1>*Dz2zA$9inI0Dvk6Ak7dz z4qFD=o^Y=H#NO@_nILfanj}K&I8jynMnX!@f6QeKv$h#|W6wOFT}tiwaK4^h;-YNw z>T0v*!aj!`{`ae}Nyu#!s~a{BdGZ9QgrVAr9hnKbLa)-!$aYLL^{$K>iMHGT>8sO} zdx&Qf+tG)E+C$_%#H<{rvrdWNMIe6df@XAqk-Y{c`OnW z*3n00EU~&8QsS9ojAEvI0B{!)c%ihl((E(6wZSiJC9jk@Ar=5Ugao4852NF(#eE@= zg*Y5~FF)-?(69ROLe{&Fg4yQU{TX=kV&}(w5d{RCa57)XC-6IsAtb>47`4+6_stQ3`Tk`vl8{qKhk zho^YClfF*f-^trzZ+me1fvy!UvrM&{d5`l-cddoJ`kT0Df+Ebkrqt-Xp6XbqYh5SKgcrL?@)L{Iv+ z5pdZ1+1HA{Ph(Z@EQTvrtKB9w=UN&14;8LFwvD1(+Z~lR6ivN89pe?U`;#Y8T>X7-%XBj0Njk80S!Cz9nV*zv~!Fu43Ux zJ^^k764u8vz>WAfaWN^~R?Tn;WaDLcg=%Esb8E>0g^NnFLTb7SmYPzTx4d+#O=V0R zwbd0qk~KmB&@Mg7Vu{Gh9M{8jBQ%5VFZ}hk$6VweedZrq0OKFcd}NlM*x5DdqqJvP zp@rsE$HXcyk?!pbtsRC{MpE9&O4OWmRNIl5hRbc>Cd3 zFGz(ILU%xO(uu90wCGHkPFv?$%;fQQy4R->!Tw@{0a*9Z%1;Whj_nRXHQm7CVBFHj z+Li{3MSlajMQk9VZ;)M6L00b9HWmh?96O1#Hi2|jlYQ+bhyK8I7~S^$|`vs`cm}jLFm0>2z%)a^MNzhX|%AA&x>~f zr^Zy;BQ$Z-do9K>Dkh}XdfWEK8P;HZuaOg~h)o4omX_L$dDx4J?Q{=yw(xKwE;i1}PMsZV^?OR7 z#Ir+S;n+0iV2q2ZpSac{nBWI(4b0m6!at5G6Ak*efZ;O#mu^MxTVZe#7yUoGb*-!N z%Koaxd3@2h{0%hjDsy5fhstPp%Sy>%wmNO0bCA&b0f?RyM4_TELh8Z&d;g&SGlh(N8HE)*5mOGCRY(xW1HZ5yv_A-Jt~W*r@2GD8g$@# n)D1>_JsfWrt^LVb&?a!-md&-r5#$^W__7s8aOkIkxo>{~wmB}3 diff --git a/build/shared/libraries/minim/library/minim.jar b/build/shared/libraries/minim/library/minim.jar deleted file mode 100755 index eb053ccecb81f4f9c42ab803f62ffa1d11d7aab4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51439 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 ior=-=Khd6VAnit!Z2H`hRr4bMJfS`|f(RR#aA0 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 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 4a02386d4bff2de1effc6fda864b2724b07153ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10401 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≀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 - 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. + *

    + *
  • Do not use "new PGraphicsXxxx()", use this method. This method + * ensures that internal variables are set up properly that tie the + * new graphics context back to its parent PApplet. + *
  • The basic way to create bitmap images is to use the saveFrame() + * function. + *
  • If you want to create a really large scene and write that, + * first make sure that you've allocated a lot of memory in the Preferences. + *
  • If you want to create images that are larger than the screen, + * you should create your own PGraphics object, draw to that, and use + * save(). + * For now, it's best to use P3D in this scenario. + * P2D is currently disabled, and the JAVA2D default will give mixed + * results. An example of using P3D: + *
    +   *
    +   * PGraphics big;
    +   *
    +   * void setup() {
    +   *   big = createGraphics(3000, 3000, P3D);
    +   *
    +   *   big.beginDraw();
    +   *   big.background(128);
    +   *   big.line(20, 1800, 1800, 900);
    +   *   // etc..
    +   *   big.endDraw();
    +   *
    +   *   // make sure the file is written to the sketch folder
    +   *   big.save("big.tif");
    +   * }
    +   *
    +   * 
    + *
  • It's important to always wrap drawing to createGraphics() with + * beginDraw() and endDraw() (beginFrame() and endFrame() prior to + * revision 0115). The reason is that the renderer needs to know when + * drawing has stopped, so that it can update itself internally. + * This also handles calling the defaults() method, for people familiar + * with that. + *
  • It's not possible to use createGraphics() with the OPENGL renderer, + * because it doesn't allow offscreen use. + *
  • With Processing 0115 and later, it's possible to write images in + * formats other than the default .tga and .tiff. The exact formats and + * background information can be found in the developer's reference for + * PImage.save(). + *
+ */ + 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: + *

    + *
  • TGA and TIFF images are loaded using the internal load methods. + *
  • JPG, GIF, and PNG images are loaded via loadBytes(). + *
  • If the image still isn't loaded, it's passed to javax.imageio. + *
+ * 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: + *

    + *
  • A URL, for instance openStream("http://processing.org/"); + *
  • A file in the sketch's data folder + *
  • Another file to be opened locally (when running as an application) + *
+ */ + 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. + *
    + *
  • setParent(PApplet) - is called to specify the parent PApplet. + *
  • setPrimary(boolean) - called with true if this PGraphics will be the + * primary drawing surface used by the sketch, or false if not. + *
  • setPath(String) - called when the renderer needs a filename or output + * path, such as with the PDF or DXF renderers. + *
  • setSize(int, int) - this is called last, at which point it's safe for + * the renderer to complete its initialization routine. + *
+ * 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: + *

    + *
  • DISABLE_DEPTH_TEST - + * turns off the z-buffer in the P3D or OPENGL renderers. + *
+ */ + 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. + *

+ *

    + *
  • filter(BLUR) provides a basic blur. + *
  • filter(GRAY) converts the image to grayscale based on luminance. + *
  • filter(INVERT) will invert the color components in the image. + *
  • filter(OPAQUE) set all the high bits in the image to opaque + *
  • filter(THRESHOLD) converts the image to black and white. + *
  • filter(DILATE) grow white/light areas + *
  • filter(ERODE) shrink white/light areas + *
+ * 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. + *

+ *

    + *
  • filter(BLUR, int radius) performs a gaussian blur of the + * specified radius. + *
  • filter(POSTERIZE, int levels) will posterize the image to + * between 2 and 255 levels. + *
  • filter(THRESHOLD, float center) allows you to set the + * center point for the threshold. It takes a value from 0 to 1.0. + *
+ * 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 0000000000000000000000000000000000000000..e025d6a25e221cf9bdff231c440076a4baccaad7 GIT binary patch literal 3943 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

    _PJ5bbYVCzJBSneT&$i=VVDV&5jaF%08U-NsCp zV|ouK0&48$53Ok_5u7$C$om;y>e&JPj6j+6PNN`emV5B!f_EC}c>MQA#c?-mD=jU| zk*CsE7u+amlkTHViFSq|&fJyBfktI4MZO0=jLpr*6_J6&LKh+m0;lJoAaO|qvEHS( z59uVA(1jytw<^`El&uRn^UWHeo&oYfKp7>^;gxXUxH)vNOJq#l&_lc@o`)_lzlHm?>RD;n5j`ZhsmS)v4!m{zb z;om%33ugILT~*uX=s-G<*K9>xM#3j%Ib7&+&9k=&L$}XjEgDKQgY_=ovBW;yD)&8S zXXYh1^CMU@guy0#J=koY-5X+m75`CY8WWP@oA~r!2`g zG>7p7#tRm)dDS)mwf)+c$tXKVH{_0Acb1;gJiY50FVXW3G3qeD^C>ma;S!7?rZZ?^ zLrI%qB02= 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?