From 076ed334528415eb055976093b9991ef9832b5c8 Mon Sep 17 00:00:00 2001 From: benfry Date: Mon, 4 Apr 2005 17:26:43 +0000 Subject: [PATCH] moving the libraries --- core/todo.txt | 11 +- net/Client.java | 442 +++++++++++ net/Server.java | 233 ++++++ net/library/.cvsignore | 1 + opengl/PGraphicsGL.java | 1451 +++++++++++++++++++++++++++++++++++ opengl/library/.cvsignore | 1 + serial/Serial.java | 644 ++++++++++++++++ serial/macosx_setup.command | 51 ++ todo.txt | 14 +- video/Camera.java | 473 ++++++++++++ video/Movie.java | 571 ++++++++++++++ video/library/.cvsignore | 1 + 12 files changed, 3885 insertions(+), 8 deletions(-) create mode 100644 net/Client.java create mode 100644 net/Server.java create mode 100644 net/library/.cvsignore create mode 100644 opengl/PGraphicsGL.java create mode 100644 opengl/library/.cvsignore create mode 100644 serial/Serial.java create mode 100755 serial/macosx_setup.command create mode 100644 video/Camera.java create mode 100644 video/Movie.java create mode 100644 video/library/.cvsignore diff --git a/core/todo.txt b/core/todo.txt index 00549fc2c..aeec05ac2 100644 --- a/core/todo.txt +++ b/core/todo.txt @@ -8,6 +8,7 @@ X this way can avoid duplicating / breaking things o what is the stroked version of a sphere? a circle? X write list of params that can be passed to PApplet o document in the code a note about how size() et al place themselves +X saveFrame was double-adding the save path because of save() changes size(200, 200, P3D) - createGraphics and placement issues X make pappletgl work back inside papplet @@ -28,6 +29,8 @@ X can this be done with g2 and if exception just falls back to g1? X this way people can remove g1 by hand o size() that changes renderer will throw nasty exception in draw() X or maybe that's ok? document that no changing renderers? +X take a look to see what needs to happen to get PAppletGL merged in +X i.e. can i just extend GLCanvas? present mode o call present() from inside the code? @@ -50,6 +53,9 @@ X make sure the program compiles before starting present mode _ fix the flicker in java2d mode X is it because the lock was taken off (g) in PApplet? +_ apply tint() to textures as well +_ otherwise no good way to color textures + _ cellular automata examples broken _ gl smoothing.. how to disable polygon but keep line enabled @@ -71,15 +77,12 @@ _ make illustrator lib BETA -_ take a look to see what needs to happen to get PAppletGL merged in -_ i.e. can i just extend GLCanvas? +_ get PGraphics.java engine working again _ don't grab pixels of java2d images unless asked _ this is the difference between a lot of loadPixels() and not _ so important to have it in before beta if that's the change -_ get PGraphics.java engine working again - _ if (modified) don't loadPixels again, just ignore it _ make a note that updatePixels() only sets a flag in PImage _ (but not PGraphics, which does it immediately) diff --git a/net/Client.java b/net/Client.java new file mode 100644 index 000000000..32be73bd4 --- /dev/null +++ b/net/Client.java @@ -0,0 +1,442 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + PClient - basic network client implementation + Part of the Processing project - http://processing.org + + Copyright (c) 2004 Ben Fry + The previous version of this code was developed by Hernando Barragan + + 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.net; +import processing.core.*; + +import java.awt.event.*; +import java.util.*; +import java.awt.*; +import java.io.*; +import java.lang.reflect.*; +import java.net.*; + + +public class Client implements Runnable { + + PApplet parent; + Method clientEventMethod; + + Thread thread; + Socket socket; + String ip; + int port; + String host; + + + // read buffer and streams + + InputStream input; + OutputStream output; + + byte buffer[] = new byte[32768]; + int bufferIndex; + int bufferLast; + + + public Client(PApplet parent, String host, int port) { + this.parent = parent; + this.host = host; + this.port = port; + + //parent.attach(this); + + try { + socket = new Socket(this.host, this.port); + input = socket.getInputStream(); + output = socket.getOutputStream(); + + thread = new Thread(this); + thread.start(); + + parent.registerDispose(this); + + // reflection to check whether host applet has a call for + // public void serialEvent(processing.serial.Serial) + // which would be called each time an event comes in + try { + clientEventMethod = + parent.getClass().getMethod("clientEvent", + new Class[] { Client.class }); + } catch (Exception e) { + // no such method, or an error.. which is fine, just ignore + } + + } catch (IOException e) { + errorMessage("", e); + } + } + + + public Client(PApplet parent, Socket socket) throws IOException { + this.socket = socket; + + input = socket.getInputStream(); + output = socket.getOutputStream(); + + thread = new Thread(this); + thread.start(); + } + + + /** + * Disconnect from the server. + *

+ * Use this to shut the connection if you're finished with it + * while your applet is still running. Otherwise, it will be + * automatically be shut down by the host PApplet + * (using dispose, which is identical) + */ + public void stop() { + dispose(); + } + + + /** + * Disconnect from the server: internal use only. + *

+ * This should only be called by the internal functions in PApplet, + * use stop() instead from within your own applets. + */ + public void dispose() { + try { + // do io streams need to be closed first? + if (input != null) input.close(); + if (output != null) output.close(); + + } catch (Exception e) { + e.printStackTrace(); + } + input = null; + output = null; + + try { + if (socket != null) socket.close(); + + } catch (Exception e) { + e.printStackTrace(); + } + socket = null; + } + + + public void run() { + while (Thread.currentThread() == thread) { + try { + while (input.available() > 0) { // this will block + synchronized (buffer) { + if (bufferLast == buffer.length) { + byte temp[] = new byte[bufferLast << 1]; + System.arraycopy(buffer, 0, temp, 0, bufferLast); + buffer = temp; + } + buffer[bufferLast++] = (byte) input.read(); + } + } + // now post an event + if (clientEventMethod != null) { + try { + clientEventMethod.invoke(parent, new Object[] { this }); + } catch (Exception e) { + System.err.println("error, disabling clientEvent() for " + host); + e.printStackTrace(); + clientEventMethod = null; + } + } + + try { + // uhh.. not sure what's best here.. since blocking, + // do we need to worry about sleeping much? or is this + // gonna try to slurp cpu away from the main applet? + Thread.sleep(10); + } catch (InterruptedException ex) { } + + } catch (IOException e) { + errorMessage("run", e); + } + } + } + + + /** + * Returns the ip address of this feller as a String. + */ + public String ip() { + return socket.getInetAddress().getHostAddress(); + } + + + /** + * Returns the number of bytes that have been read from serial + * and are waiting to be dealt with by the user. + */ + public int available() { + return (bufferLast - bufferIndex); + } + + + /** + * Ignore all the bytes read so far and empty the buffer. + */ + public void clear() { + bufferLast = 0; + bufferIndex = 0; + } + + + /** + * Returns a number between 0 and 255 for the next byte that's + * waiting in the buffer. + * Returns -1 if there was no byte (although the user should + * first check available() to see if things are ready to avoid this) + */ + public int read() { + if (bufferIndex == bufferLast) return -1; + + synchronized (buffer) { + int outgoing = buffer[bufferIndex++] & 0xff; + if (bufferIndex == bufferLast) { // rewind + bufferIndex = 0; + bufferLast = 0; + } + return outgoing; + } + } + + + /** + * Returns the next byte in the buffer as a char. + * Returns -1, or 0xffff, if nothing is there. + */ + public char readChar() { + if (bufferIndex == bufferLast) return (char)(-1); + return (char) read(); + } + + + /** + * Return a byte array of anything that's in the serial buffer. + * Not particularly memory/speed efficient, because it creates + * a byte array on each read, but it's easier to use than + * readBytes(byte b[]) (see below). + */ + public byte[] readBytes() { + if (bufferIndex == bufferLast) return null; + + synchronized (buffer) { + int length = bufferLast - bufferIndex; + byte outgoing[] = new byte[length]; + System.arraycopy(buffer, bufferIndex, outgoing, 0, length); + + bufferIndex = 0; // rewind + bufferLast = 0; + return outgoing; + } + } + + + /** + * Grab whatever is in the serial buffer, and stuff it into a + * byte buffer passed in by the user. This is more memory/time + * efficient than readBytes() returning a byte[] array. + * + * Returns an int for how many bytes were read. If more bytes + * are available than can fit into the byte array, only those + * that will fit are read. + */ + public int readBytes(byte outgoing[]) { + if (bufferIndex == bufferLast) return 0; + + synchronized (buffer) { + int length = bufferLast - bufferIndex; + if (length > outgoing.length) length = outgoing.length; + System.arraycopy(buffer, bufferIndex, outgoing, 0, length); + + bufferIndex += length; + if (bufferIndex == bufferLast) { + bufferIndex = 0; // rewind + bufferLast = 0; + } + return length; + } + } + + + /** + * Reads from the serial port into a buffer of bytes up to and + * including a particular character. If the character isn't in + * the serial buffer, then 'null' is returned. + */ + public byte[] readBytesUntil(int interesting) { + if (bufferIndex == bufferLast) return null; + byte what = (byte)interesting; + + synchronized (buffer) { + int found = -1; + for (int k = bufferIndex; k < bufferLast; k++) { + if (buffer[k] == what) { + found = k; + break; + } + } + if (found == -1) return null; + + int length = found - bufferIndex + 1; + byte outgoing[] = new byte[length]; + System.arraycopy(buffer, bufferIndex, outgoing, 0, length); + + bufferIndex = 0; // rewind + bufferLast = 0; + return outgoing; + } + } + + + /** + * Reads from the serial port into a buffer of bytes until a + * particular character. If the character isn't in the serial + * buffer, then 'null' is returned. + * + * If outgoing[] is not big enough, then -1 is returned, + * and an error message is printed on the console. + * If nothing is in the buffer, zero is returned. + * If 'interesting' byte is not in the buffer, then 0 is returned. + */ + public int readBytesUntil(int interesting, byte outgoing[]) { + if (bufferIndex == bufferLast) return 0; + byte what = (byte)interesting; + + synchronized (buffer) { + int found = -1; + for (int k = bufferIndex; k < bufferLast; k++) { + if (buffer[k] == what) { + found = k; + break; + } + } + if (found == -1) return 0; + + int length = found - bufferIndex + 1; + if (length > outgoing.length) { + System.err.println("readBytesUntil() byte buffer is" + + " too small for the " + length + + " bytes up to and including char " + interesting); + return -1; + } + //byte outgoing[] = new byte[length]; + System.arraycopy(buffer, bufferIndex, outgoing, 0, length); + + bufferIndex += length; + if (bufferIndex == bufferLast) { + bufferIndex = 0; // rewind + bufferLast = 0; + } + return length; + } + } + + + /** + * Return whatever has been read from the serial port so far + * as a String. It assumes that the incoming characters are ASCII. + * + * If you want to move Unicode data, you can first convert the + * String to a byte stream in the representation of your choice + * (i.e. UTF8 or two-byte Unicode data), and send it as a byte array. + */ + public String readString() { + if (bufferIndex == bufferLast) return null; + return new String(readBytes()); + } + + + /** + * Combination of readBytesUntil and readString. See caveats in + * each function. Returns null if it still hasn't found what + * you're looking for. + * + * If you want to move Unicode data, you can first convert the + * String to a byte stream in the representation of your choice + * (i.e. UTF8 or two-byte Unicode data), and send it as a byte array. + */ + public String readStringUntil(int interesting) { + byte b[] = readBytesUntil(interesting); + if (b == null) return null; + return new String(b); + } + + + /** + * This will handle both ints, bytes and chars transparently. + */ + public void write(int what) { // will also cover char + try { + output.write(what & 0xff); // for good measure do the & + output.flush(); // hmm, not sure if a good idea + + } catch (Exception e) { // null pointer or serial port dead + errorMessage("write", e); + } + } + + + public void write(byte bytes[]) { + try { + output.write(bytes); + output.flush(); // hmm, not sure if a good idea + + } catch (Exception e) { // null pointer or serial port dead + //errorMessage("write", e); + e.printStackTrace(); + } + } + + + /** + * Write a String to the output. Note that this doesn't account + * for Unicode (two bytes per char), nor will it send UTF8 + * characters.. It assumes that you mean to send a byte buffer + * (most often the case for networking and serial i/o) and + * will only use the bottom 8 bits of each char in the string. + * (Meaning that internally it uses String.getBytes) + * + * If you want to move Unicode data, you can first convert the + * String to a byte stream in the representation of your choice + * (i.e. UTF8 or two-byte Unicode data), and send it as a byte array. + */ + public void write(String what) { + write(what.getBytes()); + } + + + /** + * General error reporting, all corraled here just in case + * I think of something slightly more intelligent to do. + */ + public void errorMessage(String where, Exception e) { + parent.die("Error inside Client." + where + "()", e); + //e.printStackTrace(System.err); + } +} diff --git a/net/Server.java b/net/Server.java new file mode 100644 index 000000000..a64150e4e --- /dev/null +++ b/net/Server.java @@ -0,0 +1,233 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + PServer - basic network server implementation + Part of the Processing project - http://processing.org + + Copyright (c) 2004 Ben Fry + The previous version of this code was developed by Hernando Barragan + + 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.net; +import processing.core.*; + +import java.awt.event.*; +import java.util.*; +import java.awt.*; +import java.io.*; +import java.lang.reflect.*; +import java.net.*; + + +public class Server implements Runnable { + + PApplet parent; + Method serverEventMethod; + + Thread thread; + ServerSocket server; + int port; + Vector clients; // people payin the bills + + + public Server(PApplet parent, int port) { + this.parent = parent; + this.port = port; + + //parent.attach(this); + + try { + server = new ServerSocket(this.port); + clients = new Vector(); + + thread = new Thread(this); + thread.start(); + + parent.registerDispose(this); + + // reflection to check whether host applet has a call for + // public void serverEvent(Server s, Client c); + // which is called when a new guy connects + try { + serverEventMethod = + parent.getClass().getMethod("serverEvent", + new Class[] { Server.class, + Client.class }); + } catch (Exception e) { + // no such method, or an error.. which is fine, just ignore + } + + } catch (IOException e) { + errorMessage("", e); + } + } + + + /** + * Disconnect a particular client. + */ + public void disconnect(Client client) { + //client.stop(); + client.dispose(); + clients.removeElement(client); + } + + + // the last index used for available. can't just cycle through + // the clients in order from 0 each time, because if client 0 won't + // shut up, then the rest of the clients will never be heard from. + int lastAvailable = -1; + + /** + * Returns the next client in line that has something to say. + */ + public Client available() { + synchronized (clients) { + int clientCount = clients.size(); + int index = lastAvailable + 1; + if (index >= clientCount) index = 0; + + for (int i = 0; i < clientCount; i++) { + int which = (index + i) % clientCount; + Client client = (Client) clients.elementAt(which); + if (client.available() > 0) { + lastAvailable = which; + return client; + } + } + } + return null; + } + + + /** + * Disconnect all clients and stop the server. + *

+ * Use this to shut down the server if you finish using it + * while your applet is still running. Otherwise, it will be + * automatically be shut down by the host PApplet + * (using dispose, which is identical) + */ + public void stop() { + dispose(); + } + + + /** + * Disconnect all clients and stop the server: internal use only. + */ + public void dispose() { + try { + thread = null; + + if (clients != null) { + Enumeration en = clients.elements(); + while (en.hasMoreElements()) { + disconnect((Client) en.nextElement()); + } + clients = null; + } + + if (server != null) { + server.close(); + server = null; + } + + } catch (IOException e) { + errorMessage("stop", e); + } + } + + + public void run() { + while (Thread.currentThread() == thread) { + try { + Socket socket = server.accept(); + Client client = new Client(parent, socket); + synchronized (clients) { + clients.addElement(client); + if (serverEventMethod != null) { + try { + serverEventMethod.invoke(parent, new Object[] { this, client }); + } catch (Exception e) { + System.err.println("error, disabling serverEvent() " + + " for port " + port); + e.printStackTrace(); + serverEventMethod = null; + } + } + } + } catch (IOException e) { + errorMessage("run", e); + } + try { + Thread.sleep(8); + } catch (InterruptedException ex) { } + } + } + + + /** + * Write a value to all the connected clients. + * See Client.write() for operational details. + */ + public void write(int what) { // will also cover char + Enumeration en = clients.elements(); + while (en.hasMoreElements()) { + Client client = (Client) en.nextElement(); + client.write(what); + } + } + + + /** + * Write a byte array to all the connected clients. + * See Client.write() for operational details. + */ + public void write(byte bytes[]) { + Enumeration en = clients.elements(); + while (en.hasMoreElements()) { + Client client = (Client) en.nextElement(); + client.write(bytes); + } + } + + + /** + * Write a String to all the connected clients. + * See Client.write() for operational details. + */ + public void write(String what) { + Enumeration en = clients.elements(); + while (en.hasMoreElements()) { + Client client = (Client) en.nextElement(); + client.write(what); + } + } + + + /** + * General error reporting, all corraled here just in case + * I think of something slightly more intelligent to do. + */ + public void errorMessage(String where, Exception e) { + parent.die("Error inside Server." + where + "()", e); + //System.err.println("Error inside Server." + where + "()"); + //e.printStackTrace(System.err); + } +} diff --git a/net/library/.cvsignore b/net/library/.cvsignore new file mode 100644 index 000000000..b840c4430 --- /dev/null +++ b/net/library/.cvsignore @@ -0,0 +1 @@ +net.jar diff --git a/opengl/PGraphicsGL.java b/opengl/PGraphicsGL.java new file mode 100644 index 000000000..10b0e663e --- /dev/null +++ b/opengl/PGraphicsGL.java @@ -0,0 +1,1451 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + PGraphicsGL - opengl version of the graphics engine + Part of the Processing project - http://processing.org + + Copyright (c) 2004-05 Ben Fry and Casey Reas + + 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.opengl; + +import processing.core.*; +import net.java.games.jogl.*; + + +public class PGraphicsGL extends PGraphics3 { + public GL gl; + public GLU glu; + public GLCanvas canvas; + + /** + * true if the host system is big endian (mac, irix, sun), + * false if little endian (intel). + */ + static public boolean BIG_ENDIAN = + System.getProperty("sun.cpu.endian").equals("big"); + + + /** + * Create a new PGraphicsGL at the specified size. + * @param applet the host applet, cannot be null + */ + public PGraphicsGL(int width, int height, PApplet applet) { + //System.out.println("creating PGraphicsGL"); + + if (applet == null) { + throw new RuntimeException("The applet passed to PGraphicsGL " + + "cannot be null."); + } + + //System.out.println("creating PGraphicsGL 2"); + + GLCapabilities capabilities = new GLCapabilities(); + canvas = GLDrawableFactory.getFactory().createGLCanvas(capabilities); + + //System.out.println("creating PGraphicsGL 3"); + + final PApplet parent = applet; + canvas.addGLEventListener(new GLEventListener() { + + public void display(GLDrawable drawable) { + parent.display(); // this means it's time to go + } + + public void init(GLDrawable drawable) { } + + public void displayChanged(GLDrawable drawable, + boolean modeChanged, + boolean deviceChanged) { } + + public void reshape(GLDrawable drawable, + int x, int y, int width, int height) { } + }); + + //System.out.println("creating PGraphicsGL 4"); + + applet.setLayout(null); + applet.add(canvas); + canvas.setBounds(0, 0, width, height); + + //System.out.println("creating PGraphicsGL 5"); + + canvas.addMouseListener(applet); + canvas.addMouseMotionListener(applet); + canvas.addKeyListener(applet); + canvas.addFocusListener(applet); + + //System.out.println("creating PGraphicsGL 6"); + + // need to get proper opengl context since will be needed below + gl = canvas.getGL(); + glu = canvas.getGLU(); + + //System.out.println("creating PGraphicsGL 7"); + + // this sets width/height and calls allocate() in PGraphics + resize(width, height); + //defaults(); // call this just before setup instead + + //System.out.println("done creating gl"); + } + + + protected boolean displayed = false; + + // main applet thread requests an update, + // but PGraphics has to respond back by calling PApplet.display() + public void requestDisplay(PApplet parent) { + //System.out.println("requesting display"); + + if (!displayed) { + // these two method calls (and explanations) were taken from + // the FpsAnimator implementation from the jogl hoo-ha + + // Try to get OpenGL context optimization since we know we + // will be rendering this one drawable continually from + // this thread; make the context current once instead of + // making it current and freeing it each frame. + canvas.setRenderingThread(Thread.currentThread()); + //System.out.println(Thread.currentThread()); + + // Since setRenderingThread is currently advisory (because + // of the poor JAWT implementation in the Motif AWT, which + // performs excessive locking) we also prevent repaint(), + // which is called from the AWT thread, from having an + // effect for better multithreading behavior. This call is + // not strictly necessary, but if end users write their + // own animation loops which update multiple drawables per + // tick then it may be necessary to enforce the order of + // updates. + canvas.setNoAutoRedrawMode(true); + + // done with this business + displayed = true; + } + // request a display from the gl canvas. when it happens, + // we'll hear about it from the GLEventListener, which will + // in turn call PApplet.display()... hold your breath... + try { + canvas.display(); + } catch (Exception e) { + //} catch (InterruptedException e) { + e.printStackTrace(); + } + } + + + /* + // this was additional stuff used when the animator + // thread was being shut down.. how to handle this.. + + drawable.setNoAutoRedrawMode(false); + try { + // The surface is already unlocked and rendering + // thread is already null if an exception occurred + // during display(), so don't disable the rendering + // thread again. + if (noException) { + drawable.setRenderingThread(null); + } + } finally { + synchronized (PAppletThreadGL.this) { + thread = null; + PAppletThreadGL.this.notify(); + } + } + */ + + + /** + * Called by resize(), but nothing to allocate for an OpenGL canvas. + */ + protected void allocate() { + // nothing to do here just yet + // normally this allocates the pixel buffer, etc. + } + + + // public void defaults() { } + // this sets up the positions of the two base lights + // not sure if this needs to be enabled in opengl + + + public void beginFrame() { + super.beginFrame(); + //resetMatrix(); + //normal(0, 0, 1); + //System.out.println("beginFrame() start error " + PApplet.hex(gl.glGetError())); + + report("top beginFrame()"); + + gl.glMatrixMode(GL.GL_MODELVIEW); + gl.glLoadIdentity(); + // gl coordinates are reversed + gl.glTranslatef(0, height, 0); + gl.glScalef(1, -1, 1); + //gl.glPushMatrix(); + + // these are necessary for alpha (i.e. fonts) to work + gl.glEnable(GL.GL_BLEND); + gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); + + // this is necessary for 3D drawing + gl.glEnable(GL.GL_DEPTH_TEST); + // use <= since that's what processing.core does + gl.glDepthFunc(GL.GL_LEQUAL); + + // I never really got the hang of lighting in OpenGL ... + // I've done something like [the following] which at least + // demonstrates that it works... --tom carden + + // because y is flipped + gl.glFrontFace(GL.GL_CW); + + // coloured stuff + gl.glEnable(GL.GL_COLOR_MATERIAL); + gl.glColorMaterial(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT_AND_DIFFUSE); + gl.glColorMaterial(GL.GL_FRONT_AND_BACK, GL.GL_SPECULAR); + + // these tend to make life easier + // (but sometimes at the expense of a little speed) + gl.glEnable(GL.GL_NORMALIZE); + gl.glEnable(GL.GL_AUTO_NORMAL); // I think this is OpenGL 1.2 only + gl.glEnable(GL.GL_RESCALE_NORMAL); + + report("bot beginFrame()"); + // are there other things to do here? + //System.out.println("beginFrame() stop error " + PApplet.hex(gl.glGetError())); + } + + + public void endFrame() { + //System.out.println("endFrame() error " + PApplet.hex(gl.glGetError())); + + report("top endFrame()"); + + if (hints[DEPTH_SORT]) { + if (triangleCount > 0) { + depth_sort_triangles(); + render_triangles(); + } + if (lineCount > 0) { + depth_sort_lines(); + render_lines(); + } + } + //gl.glPopMatrix(); + + report("bot endFrame()"); + } + + + protected void render_triangles() { + //public void render_triangles() { + report("into triangles"); + //System.out.println("into triangles error " + PApplet.hex(gl.glGetError())); + + //System.out.println("rendering " + triangleCount + " triangles"); + + for (int i = 0; i < triangleCount; i ++) { + //System.out.println(" rendering triangle " + i); + + float a[] = vertices[triangles[i][VERTEX1]]; + float b[] = vertices[triangles[i][VERTEX2]]; + float c[] = vertices[triangles[i][VERTEX3]]; + + int textureIndex = triangles[i][TEXTURE_INDEX]; + if (textureIndex != -1) { + //System.out.println("texture drawing"); + + PImage texture = textures[textureIndex]; + report("before enable"); + //gl.glEnable(GL.GL_TEXTURE_2D); + report("after enable"); + + ImageCache cash = (ImageCache) texture.cache; // as in johnny + if (cash == null) { + // make sure this thing is cached and a power of 2 + //if (texture.cache == null) { + cache(texture); + cash = (ImageCache) texture.cache; + // mark for update (nope, already updated) + //texture.updatePixels(0, 0, texture.width, texture.height); + //texture.modified = true; + + } else if (texture.modified) { + // TODO make this more efficient and just update a sub-part + // based on mx1 et al, also use gl function to update + // only a sub-portion of the image. + //cash.update(texture.pixels, texture.width, texture.height); + cash.update(texture); + + gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, 4, + cash.twidth, cash.theight, + 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, + //0, GL.GL_BGRA_EXT, GL.GL_UNSIGNED_BYTE, + cash.tpixels); + + report("re-binding " + cash.twidth + " " + + cash.theight + " " + cash.tpixels); + gl.glTexParameterf(GL.GL_TEXTURE_2D, + GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP); + gl.glTexParameterf(GL.GL_TEXTURE_2D, + GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP); + gl.glTexParameterf(GL.GL_TEXTURE_2D, + GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR); + gl.glTexParameterf(GL.GL_TEXTURE_2D, + GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR); + + report("re-binding 3"); + gl.glTexEnvf(GL.GL_TEXTURE_ENV, + GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE); + + // actually bind this feller + texture.modified = false; + } + + report("before bind"); + //System.out.println(gl.glIsTexture(image.tindex)); + + //GL_PERSPECTIVE_CORRECTION_HINT to GL_NICEST + // and running the example again. To do this, use glHint(). + + // these don't seem to do much + //gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1); + //gl.glPixelStorei(GL.GL_UNPACK_SWAP_BYTES, 1); + + int tindex = ((ImageCache) texture.cache).tindex; + gl.glBindTexture(GL.GL_TEXTURE_2D, tindex); + + //if (image.format == ALPHA) { + //System.out.println("binding with replace"); + // gl.glTexEnvf(GL.GL_TEXTURE_ENV, + // GL.GL_TEXTURE_ENV_MODE, GL.GL_REPLACE); + //} else { + //gl.glTexEnvf(GL.GL_TEXTURE_ENV, + // GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE); + //} + + report("after bind"); + + /* + } else { + cache(texture); + cash = (ImageCache) texture.cache; + + report("non-binding 0"); + // may be needed for windows + report("non-binding 1"); + + gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, 4, + cash.twidth, cash.theight, + 0, GL.GL_BGRA_EXT, GL.GL_UNSIGNED_BYTE, + cash.tpixels); + + report("non-binding 2 " + cash.twidth + " " + + cash.theight + " " + cash.tpixels); + gl.glTexParameterf(GL.GL_TEXTURE_2D, + GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP); + gl.glTexParameterf(GL.GL_TEXTURE_2D, + GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP); + gl.glTexParameterf(GL.GL_TEXTURE_2D, + GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR); + gl.glTexParameterf(GL.GL_TEXTURE_2D, + GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR); + + report("non-binding 3"); + gl.glTexEnvf(GL.GL_TEXTURE_ENV, + GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE); + } + */ + + float uscale = (float) texture.width / (float) cash.twidth; + float vscale = (float) texture.height / (float) cash.theight; + + gl.glEnable(GL.GL_TEXTURE_2D); + + gl.glBegin(GL.GL_TRIANGLES); + + gl.glColor4f(a[R], a[G], a[B], a[A]); + gl.glTexCoord2f(a[U] * uscale, a[V] * vscale); + gl.glVertex3f(a[VX], a[VY], a[VZ]); + + gl.glColor4f(b[R], b[G], b[B], b[A]); + gl.glTexCoord2f(b[U] * uscale, b[V] * vscale); + gl.glVertex3f(b[VX], b[VY], b[VZ]); + + gl.glColor4f(c[R], c[G], c[B], c[A]); + gl.glTexCoord2f(c[U] * uscale, c[V] * vscale); + gl.glVertex3f(c[VX], c[VY], c[VZ]); + + gl.glEnd(); + report("non-binding 6"); + + gl.glDisable(GL.GL_TEXTURE_2D); + + } else { + gl.glBegin(GL.GL_TRIANGLES); + + gl.glColor4f(a[R], a[G], a[B], a[A]); + gl.glVertex3f(a[VX], a[VY], a[VZ]); + + gl.glColor4f(b[R], b[G], b[B], b[A]); + gl.glVertex3f(b[VX], b[VY], b[VZ]); + + gl.glColor4f(c[R], c[G], c[B], c[A]); + gl.glVertex3f(c[VX], c[VY], c[VZ]); + + gl.glEnd(); + } + } + report("out of triangles"); + } + + + public void render_lines() { + //System.out.println("into lines error " + PApplet.hex(gl.glGetError())); + int i = 0; + for (int j = 0; j < pathCount; j++) { + //report("render_lines 1"); + // glLineWidth has to occur outside glBegin/glEnd + gl.glLineWidth(lines[i][STROKE_WEIGHT]); + //report("render_lines 2 " + lines[i][STROKE_WEIGHT]); + gl.glBegin(GL.GL_LINE_STRIP); + + // always draw a first point + float a[] = vertices[lines[i][VERTEX1]]; + gl.glColor4f(a[SR], a[SG], a[SB], a[SA]); + gl.glVertex3f(a[VX], a[VY], a[VZ]); + + // on this and subsequent lines, only draw the second point + for (int k = 0; k < pathLength[j]; k++) { + float b[] = vertices[lines[i][VERTEX2]]; + gl.glColor4f(b[SR], b[SG], b[SB], b[SA]); + gl.glVertex3f(b[VX], b[VY], b[VZ]); + i++; + } + + gl.glEnd(); + //report("render_lines 3 " + pathLength[j]); + } + //System.out.println("outta lines error " + PApplet.hex(gl.glGetError())); + } + + + /** + * Handled entirely by OpenGL, so use this to override the superclass. + */ + protected void light_and_transform() { + } + + + ////////////////////////////////////////////////////////////// + + + /** + * Cache an image using a specified glTexName + * (name is just an integer index). + * + * If a cacheIndex is already assigned in the image, + * this request will be ignored. + */ + protected void cache(PImage image) { + if (image.cache != null) return; + + int names[] = new int[1]; + gl.glGenTextures(1, names); + int index = names[0]; + + //if (image.tindex != -1) return; + + // use glGetIntegerv with the argument GL_MAX_TEXTURE_SIZE + // to figure out min/max texture sizes + + //report("into cache"); + //image.modified(); + image.cache = new ImageCache(); + ImageCache cash = (ImageCache) image.cache; + //cash.update(image.pixels, image.width, image.height); + cash.update(image); + + // first-time binding of entire texture + + gl.glEnable(GL.GL_TEXTURE_2D); + gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1); + //gl.glPixelStorei(GL.GL_UNPACK_SWAP_BYTES, 0); + gl.glBindTexture(GL.GL_TEXTURE_2D, index); + + gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, 4, cash.twidth, cash.theight, + 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, cash.tpixels); + + gl.glTexParameterf(GL.GL_TEXTURE_2D, + //GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST); + GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR); + gl.glTexParameterf(GL.GL_TEXTURE_2D, + //GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST); + GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR); + + /* + int err = glu.gluBuild2DMipmaps(GL.GL_TEXTURE_2D, 4, + image.width, image.height, + GL.GL_RGBA, + //GL.GL_ALPHA, + GL.GL_UNSIGNED_BYTE, image.pixels); + //System.out.println("mipmap: " + err); + + gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, + GL.GL_NEAREST_MIPMAP_NEAREST); + gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, + GL.GL_NEAREST_MIPMAP_LINEAR); + */ + + //gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP); + //gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP); + + gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE); + + //gl.glDisable(GL.GL_TEXTURE_2D); + + cash.tindex = index; + } + + + class ImageCache { + int tindex; + int tpixels[]; + int twidth, theight; + + //public void update(int pixels[], int width, int height) { + public void update(PImage source) { + tindex = -1; + + // bit shifting this might be more efficient + int width2 = + (int) Math.pow(2, Math.ceil(Math.log(source.width) / Math.log(2))); + int height2 = + (int) Math.pow(2, Math.ceil(Math.log(source.height) / Math.log(2))); + + if ((width2 > twidth) || (height2 > theight)) { + // either twidth/theight are zero, or size has changed + tpixels = null; + } + if (tpixels == null) { + twidth = width2; + theight = height2; + tpixels = new int[twidth * theight]; + } + + // copy image data into the texture + int p = 0; + int t = 0; + + //if (System.getProperty("sun.cpu.endian").equals("big")) { + if (BIG_ENDIAN) { + switch (source.format) { + case ALPHA: + for (int y = 0; y < source.height; y++) { + for (int x = 0; x < source.width; x++) { + tpixels[t++] = 0xFFFFFF00 | source.pixels[p++]; + } + t += twidth - source.width; + } + break; + + case RGB: + //System.out.println("swapping RGB"); + for (int y = 0; y < source.height; y++) { + for (int x = 0; x < source.width; x++) { + int pixel = source.pixels[p++]; + tpixels[t++] = (pixel << 8) | 0xff; + //tpixels[t++] = pixel; // nice effect, actually + } + t += twidth - source.width; + } + break; + + case ARGB: + //System.out.println("gonna swap ARGB"); + for (int y = 0; y < source.height; y++) { + for (int x = 0; x < source.width; x++) { + int pixel = source.pixels[p++]; + tpixels[t++] = (pixel << 8) | ((pixel >> 24) & 0xff); + } + t += twidth - source.width; + } + break; + } + + } else { + // ARGB native, and RGBA opengl means ABGR on windows + // for the most part just need to swap two components here + // the sun.cpu.endian here might be "false", oddly enough.. + // (that's why just using an "else", rather than check for "little") + + switch (source.format) { + case ALPHA: + for (int y = 0; y < source.height; y++) { + for (int x = 0; x < source.width; x++) { + tpixels[t++] = (source.pixels[p++] << 24) | 0x00FFFFFF; + } + t += twidth - source.width; + } + break; + + case RGB: + for (int y = 0; y < source.height; y++) { + for (int x = 0; x < source.width; x++) { + int pixel = source.pixels[p++]; + // needs to be ABGR, stored in memory xRGB + // so R and B must be swapped, and the x just made FF + tpixels[t++] = + 0xff000000 | // force opacity for good measure + ((pixel & 0xFF) << 16) | + ((pixel & 0xFF0000) >> 16) | + (pixel & 0x0000FF00); + } + t += twidth - source.width; + } + break; + + case ARGB: + for (int y = 0; y < source.height; y++) { + for (int x = 0; x < source.width; x++) { + int pixel = source.pixels[p++]; + // needs to be ABGR stored in memory ARGB + // so R and B must be swapped, A and G just brought back in + tpixels[t++] = + ((pixel & 0xFF) << 16) | + ((pixel & 0xFF0000) >> 16) | + (pixel & 0xFF00FF00); + } + t += twidth - source.width; + } + break; + } + } + } + } + + + ////////////////////////////////////////////////////////////// + + + public void textMode(int mode) { + // TODO get this guy straightened out + if (mode != OBJECT) { + throw new RuntimeException("only textMode(OBJECT) is " + + "currently supported for OpenGL"); + } + super.textMode(mode); + } + + + ////////////////////////////////////////////////////////////// + + + public void endCamera() { + //System.out.println("PGraphicsGL.endCamera() 1"); + super.endCamera(); + + report("begin endCamera"); + //System.out.println("PGraphicsGL.endCamera() " + width + " " + height); + //System.exit(0); + + //System.out.println("into camera error " + PApplet.hex(gl.glGetError())); + + gl.glMatrixMode(GL.GL_PROJECTION); + //gl.glLoadIdentity(); + //System.out.println("camera should be"); + //printCamera(); + + /* + gl.glLoadMatrixf(new float[] { p00, p01, p02, p03, + p10, p11, p12, p13, + p20, p21, p22, p23, + p30, p31, p32, p33 } ); + */ + + // opengl matrices are rotated from processing's + gl.glLoadMatrixf(new float[] { p00, p10, p20, p30, + p01, p11, p21, p31, + p02, p12, p22, p32, + p03, p13, p23, p33 } ); + //gl.glScalef(1, -1, 1); + + //System.out.println("trying " + height); + // this needs to be done since the model matrix will be + // goofy since it's mid-frame and the translate/scale will be wrong + gl.glMatrixMode(GL.GL_MODELVIEW); + gl.glLoadIdentity(); + // gl coordinates are reversed + gl.glTranslatef(0, height, 0); + gl.glScalef(1, -1, 1); + + /* + float proj[] = new float[16]; + gl.glGetFloatv(GL.GL_PROJECTION_MATRIX, proj); + + //float mod[] = new float[16]; + //gl.glGetFloatv(GL.GL_MODELVIEW_MATRIX, mod); + + for (int i = 0; i < 16; i++) { + if ((i % 4) == 0) System.out.println(); + System.out.print(PApplet.nfs(proj[i], 3, 4) + " "); + } + System.out.println(); + */ + + report("out of endCamera"); + } + + + ////////////////////////////////////////////////////////////// + + + public void lights() { + super.lights(); + gl.glEnable(GL.GL_LIGHTING); + } + + public void noLights() { + super.noLights(); + gl.glDisable(GL.GL_LIGHTING); + } + + + public void lightEnable(int num) { + super.lightEnable(num); + gl.glEnable(GL.GL_LIGHT0 + num); + } + + + public void lightDisable(int num) { + super.lightDisable(num); + gl.glDisable(GL.GL_LIGHT0 + num); + } + + + public void lightPosition(int num, float x, float y, float z) { + super.lightPosition(num, x, y, z); + gl.glLightfv(GL.GL_LIGHT0 + num, + GL.GL_POSITION, new float[] { x, y, z }); + } + + + public void lightAmbient(int num, float x, float y, float z) { + super.lightAmbient(num, x, y, z); + gl.glLightfv(GL.GL_LIGHT0 + num, + GL.GL_AMBIENT, new float[] { lightAmbientR[num], + lightAmbientG[num], + lightAmbientB[num] }); + } + + + public void lightDiffuse(int num, float x, float y, float z) { + super.lightDiffuse(num, x, y, z); + gl.glLightfv(GL.GL_LIGHT0 + num, + GL.GL_DIFFUSE, new float[] { lightDiffuseR[num], + lightDiffuseG[num], + lightDiffuseB[num] }); + } + + + public void lightSpecular(int num, float x, float y, float z) { + super.lightSpecular(num, x, y, z); + gl.glLightfv(GL.GL_LIGHT0 + num, + GL.GL_SPECULAR, new float[] { lightSpecularR[num], + lightSpecularG[num], + lightSpecularB[num] }); + } + + + ////////////////////////////////////////////////////////////// + + + public void background(PImage image) { + clear(); + set(0, 0, image); + } + + + public void clear() { + float backgroundR = (float) ((backgroundColor >> 16) & 0xff) / 255.0f; + float backgroundG = (float) ((backgroundColor >> 8) & 0xff) / 255.0f; + float backgroundB = (float) (backgroundColor & 0xff) / 255.0f; + + gl.glClearColor(backgroundR, backgroundG, backgroundB, 1); + gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); + } + + + ////////////////////////////////////////////////////////////// + + + public void smooth() { + gl.glEnable(GL.GL_POINT_SMOOTH); + gl.glEnable(GL.GL_LINE_SMOOTH); + gl.glEnable(GL.GL_POLYGON_SMOOTH); + } + + + public void noSmooth() { + gl.glDisable(GL.GL_POINT_SMOOTH); + gl.glDisable(GL.GL_LINE_SMOOTH); + gl.glDisable(GL.GL_POLYGON_SMOOTH); + } + + + ////////////////////////////////////////////////////////////// + + + public void loadPixels() { + //throw new RuntimeException("loadPixels() not yet implemented for OpenGL"); + if ((pixels == null) || (pixels.length != width*height)) { + pixels = new int[width * height]; + } + + /* + for (int y = 0; y < height; y++) { + // or SKIP_PIXELS with y*width + //gl.glPixelStorei(GL.GL_PACK_SKIP_ROWS, (height-1) - y); + gl.glReadPixels(0, y, width, y + 1, + GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pixels); + } + gl.glPixelStorei(GL.GL_PACK_SKIP_ROWS, 0); + */ + + gl.glReadPixels(0, 0, width, height, + GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pixels); + + /* + int temp[] = new int[width]; + // 3 rows, skips the middle + + for (int y = 0; y < height/2; y++) { + int yy = (height - 1) - y; + System.arraycopy(pixels, y*width, temp, 0, width); + System.arraycopy(pixels, yy*width, pixels, y*width, width); + System.arraycopy(temp, 0, pixels, yy*width, width); + } + */ + + /* + // now need to swap the RGBA components to ARGB (big endian) + for (int i = 0; i < pixels.length; i++) { + //pixels[i] = ((pixels[i] & 0xff) << 24) | + pixels[i] = ((pixels[i] << 24) & 0xff) | // safer? + ((pixels[i] >> 8) & 0xffffff); + } + */ + + // flip vertically (opengl stores images upside down), + // and swap RGBA components to ARGB (big endian) + int index = 0; + int yindex = (height - 1) * width; + for (int y = 0; y < height/2; y++) { + if (BIG_ENDIAN) { + for (int x = 0; x < width; x++) { + int temp = pixels[index]; + // ignores alpha component, just sets it opaque + pixels[index] = 0xff000000 | ((pixels[yindex] >> 8) & 0x00ffffff); + pixels[yindex] = 0xff000000 | ((temp >> 8) & 0x00ffffff); + + index++; + yindex++; + } + } else { // LITTLE_ENDIAN, convert ABGR to ARGB + for (int x = 0; x < width; x++) { + int temp = pixels[index]; + + // identical to updatePixels because only two + // components are being swapped + pixels[index] = 0xff000000 | + ((pixels[yindex] << 16) & 0xff0000) | + (pixels[yindex] & 0xff00) | + ((pixels[yindex] >> 16) & 0xff); + + pixels[yindex] = 0xff000000 | + ((temp << 16) & 0xff0000) | + (temp & 0xff00) | + ((temp >> 16) & 0xff); + + index++; + yindex++; + } + } + yindex -= width*2; + } + } + + + /** + * Convert native OpenGL format into palatable ARGB format. + * This function leaves alone (ignores) the alpha component. + * Also flips the image vertically, since images are upside-down in GL. + */ + static void nativeToJavaRGB(PImage image) { + int index = 0; + int yindex = (image.height - 1) * image.width; + for (int y = 0; y < image.height/2; y++) { + if (BIG_ENDIAN) { + for (int x = 0; x < image.width; x++) { + int temp = image.pixels[index]; + // ignores alpha component, just sets it opaque + image.pixels[index] = + 0xff000000 | ((image.pixels[yindex] >> 8) & 0x00ffffff); + image.pixels[yindex] = + 0xff000000 | ((temp >> 8) & 0x00ffffff); + index++; + yindex++; + } + } else { // LITTLE_ENDIAN, convert ABGR to ARGB + for (int x = 0; x < image.width; x++) { + int temp = image.pixels[index]; + + // identical to updatePixels because only two + // components are being swapped + image.pixels[index] = 0xff000000 | + ((image.pixels[yindex] << 16) & 0xff0000) | + (image.pixels[yindex] & 0xff00) | + ((image.pixels[yindex] >> 16) & 0xff); + + image.pixels[yindex] = 0xff000000 | + ((temp << 16) & 0xff0000) | + (temp & 0xff00) | + ((temp >> 16) & 0xff); + + index++; + yindex++; + } + } + yindex -= image.width*2; + } + } + + + /** + * Convert native OpenGL format into palatable ARGB format. + * This function leaves alone (ignores) the alpha component. + * Also flips the image vertically, since images are upside-down in GL. + */ + static void nativeToJavaARGB(PImage image) { + int index = 0; + int yindex = (image.height - 1) * image.width; + for (int y = 0; y < image.height/2; y++) { + if (BIG_ENDIAN) { + for (int x = 0; x < image.width; x++) { + int temp = image.pixels[index]; + // ignores alpha component, just sets it opaque + image.pixels[index] = + (image.pixels[yindex] & 0xff000000) | + ((image.pixels[yindex] >> 8) & 0x00ffffff); + image.pixels[yindex] = + (temp & 0xff000000) | + ((temp >> 8) & 0x00ffffff); + index++; + yindex++; + } + } else { // LITTLE_ENDIAN, convert ABGR to ARGB + for (int x = 0; x < image.width; x++) { + int temp = image.pixels[index]; + + // identical to updatePixels because only two + // components are being swapped + image.pixels[index] = + (image.pixels[yindex] & 0xff000000) | + ((image.pixels[yindex] << 16) & 0xff0000) | + (image.pixels[yindex] & 0xff00) | + ((image.pixels[yindex] >> 16) & 0xff); + + image.pixels[yindex] = + (temp & 0xff000000) | + ((temp << 16) & 0xff0000) | + (temp & 0xff00) | + ((temp >> 16) & 0xff); + + index++; + yindex++; + } + } + yindex -= image.width*2; + } + } + + + /** + * Convert ARGB (Java/Processing) data to native OpenGL format. + * This function leaves alone (ignores) the alpha component. + * Also flips the image vertically, since images are upside-down in GL. + */ + static void javaToNativeRGB(PImage image) { + int width = image.width; + int height = image.height; + int pixels[] = image.pixels; + + int index = 0; + int yindex = (height - 1) * width; + for (int y = 0; y < height/2; y++) { + if (BIG_ENDIAN) { + // and convert ARGB back to opengl RGBA components (big endian) + for (int x = 0; x < image.width; x++) { + int temp = pixels[index]; + /* + pixels[index] = + ((pixels[yindex] >> 24) & 0xff) | + ((pixels[yindex] << 8) & 0xffffff00); + pixels[yindex] = + ((temp >> 24) & 0xff) | + ((temp << 8) & 0xffffff00); + */ + pixels[index] = ((pixels[yindex] << 8) & 0xffffff00) | 0xff; + pixels[yindex] = ((temp << 8) & 0xffffff00) | 0xff; + + index++; + yindex++; + } + + } else { + // convert ARGB back to native little endian ABGR + for (int x = 0; x < width; x++) { + int temp = pixels[index]; + + pixels[index] = 0xff000000 | + ((pixels[yindex] << 16) & 0xff0000) | + (pixels[yindex] & 0xff00) | + ((pixels[yindex] >> 16) & 0xff); + + pixels[yindex] = 0xff000000 | + ((temp << 16) & 0xff0000) | + (temp & 0xff00) | + ((temp >> 16) & 0xff); + + index++; + yindex++; + } + } + yindex -= width*2; + } + } + + + /** + * Convert Java ARGB to native OpenGL format. + * Also flips the image vertically, since images are upside-down in GL. + */ + static void javaToNativeARGB(PImage image) { + int width = image.width; + int height = image.height; + int pixels[] = image.pixels; + + int index = 0; + int yindex = (height - 1) * width; + for (int y = 0; y < height/2; y++) { + if (BIG_ENDIAN) { + // and convert ARGB back to opengl RGBA components (big endian) + for (int x = 0; x < image.width; x++) { + int temp = pixels[index]; + pixels[index] = + ((pixels[yindex] >> 24) & 0xff) | + ((pixels[yindex] << 8) & 0xffffff00); + pixels[yindex] = + ((temp >> 24) & 0xff) | + ((temp << 8) & 0xffffff00); + + index++; + yindex++; + } + + } else { + // convert ARGB back to native little endian ABGR + for (int x = 0; x < width; x++) { + int temp = pixels[index]; + + pixels[index] = + (pixels[yindex] & 0xff000000) | + ((pixels[yindex] << 16) & 0xff0000) | + (pixels[yindex] & 0xff00) | + ((pixels[yindex] >> 16) & 0xff); + + pixels[yindex] = + (pixels[yindex] & 0xff000000) | + ((temp << 16) & 0xff0000) | + (temp & 0xff00) | + ((temp >> 16) & 0xff); + + index++; + yindex++; + } + } + yindex -= width*2; + } + } + + + public void updatePixels() { + // flip vertically (opengl stores images upside down), + + int index = 0; + int yindex = (height - 1) * width; + for (int y = 0; y < height/2; y++) { + if (BIG_ENDIAN) { + // and convert ARGB back to opengl RGBA components (big endian) + for (int x = 0; x < width; x++) { + int temp = pixels[index]; + /* + pixels[index] = + ((pixels[yindex] >> 24) & 0xff) | + ((pixels[yindex] << 8) & 0xffffff00); + pixels[yindex] = + ((temp >> 24) & 0xff) | + ((temp << 8) & 0xffffff00); + */ + pixels[index] = ((pixels[yindex] << 8) & 0xffffff00) | 0xff; + pixels[yindex] = ((temp << 8) & 0xffffff00) | 0xff; + + index++; + yindex++; + } + + } else { + // convert ARGB back to native little endian ABGR + for (int x = 0; x < width; x++) { + int temp = pixels[index]; + + pixels[index] = 0xff000000 | + ((pixels[yindex] << 16) & 0xff0000) | + (pixels[yindex] & 0xff00) | + ((pixels[yindex] >> 16) & 0xff); + + pixels[yindex] = 0xff000000 | + ((temp << 16) & 0xff0000) | + (temp & 0xff00) | + ((temp >> 16) & 0xff); + + index++; + yindex++; + } + } + yindex -= width*2; + } + + // re-pack ARGB data into RGBA for opengl (big endian) + /* + for (int i = 0; i < pixels.length; i++) { + pixels[i] = ((pixels[i] >> 24) & 0xff) | + ((pixels[i] << 8) & 0xffffff00); + } + */ + + //System.out.println("running glDrawPixels"); + //gl.glRasterPos2i(width/2, height/2); + //gl.glRasterPos2i(width/2, 1); //height/3); + //gl.glRasterPos2i(1, height - 1); //1, 1); + + // for some reason, glRasterPos(0, height) won't draw anything. + // my guess is that it's getting "clipped", so adding an epsilon + // makes it work. also, height-1 would be the logical start, + // but apparently that's not how opengl coordinates work + gl.glRasterPos2f(0.0001f, height - 0.0001f); + + gl.glDrawPixels(width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pixels); + } + + + public void updatePixels(int x, int y, int c, int d) { + //throw new RuntimeException("updatePixels() not available with OpenGL"); + // TODO make this actually work for a smaller region + // problem is, it gets pretty messy with the y reflection, etc + updatePixels(); + } + + + ////////////////////////////////////////////////////////////// + + + int getset[] = new int[1]; + + public int get(int x, int y) { + gl.glReadPixels(x, y, 1, 1, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, getset); + + if (BIG_ENDIAN) { + return 0xff000000 | ((getset[0] >> 8) & 0x00ffffff); + + } else { + return 0xff000000 | + ((getset[0] << 16) & 0xff0000) | + (getset[0] & 0xff00) | + ((getset[0] >> 16) & 0xff); + } + //throw new RuntimeException("get() not yet implemented for OpenGL"); + //return 0; // TODO + } + + + public PImage get(int x, int y, int w, int h) { + if (imageMode == CORNERS) { // if CORNER, do nothing + w = (w - x); + h = (h - x); + } + + 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; + + PImage newbie = new PImage(w, h); //new int[w*h], w, h, ARGB); + + gl.glReadPixels(x, y, w, h, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, + newbie.pixels); + + nativeToJavaARGB(newbie); + //newbie.updatePixels(); + + /* + 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; + //throw new RuntimeException("get() not yet implemented for OpenGL"); + //return null; // TODO + } + + + public PImage get() { + return get(0, 0, width, height); + } + + + //PImage setter = new PImage(1, 1); + + public void set(int x, int y, int argb) { + if (BIG_ENDIAN) { + // convert ARGB to RGBA + getset[0] = (argb << 8) | 0xff; + + } else { + // convert ARGB to ABGR + getset[0] = + (argb & 0xff00ff00) | + ((argb << 16) & 0xff0000) | + //(argb & 0xff00) | + ((argb >> 16) & 0xff); + } + + gl.glRasterPos2f(x + EPSILON, y + EPSILON); + gl.glDrawPixels(1, 1, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, getset); + //throw new RuntimeException("set() not available with OpenGL"); + } + + + /** + * Set an image directly to the screen. + *

+ * TODO not optimized properly, creates a temporary buffer the + * size of the image. Needs to instead use image cache, but that + * requires two types of image cache. One for power of 2 textures + * and another for glReadPixels/glDrawPixels data that's flipped + * vertically. Both have their components all swapped to native. + */ + public void set(int x, int y, PImage source) { + /* + ImageCache cash = (ImageCache) source.cache; + if (cash == null) { + // this will flip the bits and make it a power of 2 + cache(source); + cash = (ImageCache) source.cache; + } + // now draw to the screen but set the scanline length + */ + int backup[] = new int[source.pixels.length]; + System.arraycopy(source.pixels, 0, backup, 0, source.pixels.length); + javaToNativeARGB(source); + + gl.glRasterPos2f(x + EPSILON, (height - y) - EPSILON); + gl.glDrawPixels(source.width, source.height, + GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, source.pixels); + //nativeToJavaARGB(source); + source.pixels = backup; + } + + + ////////////////////////////////////////////////////////////// + + + /** + * This is really inefficient and not a good idea. + * Use get() and set() with a smaller image area. + */ + public void filter(int kind) { + //throw new RuntimeException("filter() not available with OpenGL"); + PImage temp = get(); + temp.filter(kind); + set(0, 0, temp); + } + + + /** + * This is really inefficient and not a good idea. + * Use get() and set() with a smaller image area. + */ + public void filter(int kind, float param) { + //throw new RuntimeException("filter() not available with OpenGL"); + PImage temp = get(); + temp.filter(kind, param); + set(0, 0, temp); + } + + + ////////////////////////////////////////////////////////////// + + + // TODO implement these with glCopyPixels + + //public void copy(PImage src, int dx, int dy) { + //throw new RuntimeException("copy() not available with OpenGL"); + //} + + + /** + * TODO - extremely slow and not optimized. + * Currently calls a loadPixels() on the whole canvas, + * then does the copy, then it calls updatePixels(). + */ + public void copy(int sx1, int sy1, int sx2, int sy2, + int dx1, int dy1, int dx2, int dy2) { + //throw new RuntimeException("copy() not available with OpenGL"); + loadPixels(); + super.copy(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2); + updatePixels(); + } + + + /** + * TODO - extremely slow and not optimized. + * Currently calls a loadPixels() on the whole canvas, + * then does the copy, then it calls updatePixels(). + */ + public void copy(PImage src, + int sx1, int sy1, int sx2, int sy2, + int dx1, int dy1, int dx2, int dy2) { + loadPixels(); + super.copy(src, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2); + updatePixels(); + } + + + ////////////////////////////////////////////////////////////// + + + public void blend(int sx, int sy, int dx, int dy, int mode) { + set(dx, dy, PImage.blend(get(sx, sy), get(dx, dy), mode)); + //loadPixels(); + //super.blend(sx, sy, dx, dy, mode); + //updatePixels(); + } + + + public void blend(PImage src, + int sx, int sy, int dx, int dy, int mode) { + set(dx, dy, PImage.blend(src.get(sx, sy), get(dx, dy), mode)); + //loadPixels(); + //super.blend(src, sx, sy, dx, dy, mode); + //updatePixels(); + } + + + /** + * TODO - extremely slow and not optimized. + * Currently calls a loadPixels() on the whole canvas, + * then does the blend, then it calls updatePixels(). + */ + public void blend(int sx1, int sy1, int sx2, int sy2, + int dx1, int dy1, int dx2, int dy2, int mode) { + loadPixels(); + super.blend(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2, mode); + updatePixels(); + } + + + /** + * TODO - extremely slow and not optimized. + * Currently calls a loadPixels() on the whole canvas, + * then does the blend, then it calls updatePixels(). + */ + public void blend(PImage src, + int sx1, int sy1, int sx2, int sy2, + int dx1, int dy1, int dx2, int dy2, int mode) { + loadPixels(); + super.blend(src, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2, mode); + updatePixels(); + } + + + ////////////////////////////////////////////////////////////// + + + public void save(String filename) { + loadPixels(); + super.save(filename); + } + + + ////////////////////////////////////////////////////////////// + + + /** + * Report on anything from glError(). + * Don't use this inside glBegin/glEnd otherwise it'll + * throw an GL_INVALID_OPERATION error. + */ + public void report(String where) { + //if (true) return; + + int err = gl.glGetError(); + if (err == 0) { + return; + //System.out.println("no error"); + } else { + System.out.print(where + ": "); + System.out.print(PApplet.hex(err, 4) + " "); + switch (err) { + case 0x0500: System.out.print("GL_INVALID_ENUM"); break; + case 0x0501: System.out.print("GL_INVALID_VALUE"); break; + case 0x0502: System.out.print("GL_INVALID_OPERATION"); break; + case 0x0503: System.out.print("GL_STACK_OVERFLOW"); break; + case 0x0504: System.out.print("GL_STACK_UNDERFLOW"); break; + case 0x0505: System.out.print("GL_OUT_OF_MEMORY"); break; + default: System.out.print("UNKNOWN"); + } + System.out.println(); + } + } +} diff --git a/opengl/library/.cvsignore b/opengl/library/.cvsignore new file mode 100644 index 000000000..f90c72e0b --- /dev/null +++ b/opengl/library/.cvsignore @@ -0,0 +1 @@ +opengl.jar diff --git a/serial/Serial.java b/serial/Serial.java new file mode 100644 index 000000000..03de12f0f --- /dev/null +++ b/serial/Serial.java @@ -0,0 +1,644 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + PSerial - class for serial port goodness + Part of the Processing project - http://processing.org + + Copyright (c) 2004 Ben Fry & Casey Reas + + 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.serial; +import processing.core.*; + +import gnu.io.*; + +import java.io.*; +import java.util.*; +import java.lang.reflect.*; + + +public class Serial implements SerialPortEventListener { + + PApplet parent; + Method serialEventMethod; + + // properties can be passed in for default values + // otherwise defaults to 9600 N81 + + // these could be made static, which might be a solution + // for the classloading problem.. because if code ran again, + // the static class would have an object that could be closed + + public SerialPort port; + + public int rate; + public int parity; + public int databits; + public int stopbits; + + + // read buffer and streams + + public InputStream input; + public OutputStream output; + + byte buffer[] = new byte[32768]; + int bufferIndex; + int bufferLast; + + //boolean bufferUntil = false; + int bufferSize = 1; // how big before reset or event firing + boolean bufferUntil; + int bufferUntilByte; + + + // defaults + + static String dname = "COM1"; + static int drate = 9600; + static char dparity = 'N'; + static int ddatabits = 8; + static float dstopbits = 1; + + + public void setProperties(Properties props) { + dname = + props.getProperty("serial.port", dname); + drate = + Integer.parseInt(props.getProperty("serial.rate", "9600")); + dparity = + props.getProperty("serial.parity", "N").charAt(0); + ddatabits = + Integer.parseInt(props.getProperty("serial.databits", "8")); + dstopbits = + new Float(props.getProperty("serial.stopbits", "1")).floatValue(); + } + + + public Serial(PApplet parent) { + this(parent, dname, drate, dparity, ddatabits, dstopbits); + } + + public Serial(PApplet parent, int irate) { + this(parent, dname, irate, dparity, ddatabits, dstopbits); + } + + public Serial(PApplet parent, String iname, int irate) { + this(parent, iname, irate, dparity, ddatabits, dstopbits); + } + + public Serial(PApplet parent, String iname) { + this(parent, iname, drate, dparity, ddatabits, dstopbits); + } + + public Serial(PApplet parent, String iname, int irate, + char iparity, int idatabits, float istopbits) { + //if (port != null) port.close(); + this.parent = parent; + //parent.attach(this); + + this.rate = irate; + + parity = SerialPort.PARITY_NONE; + if (iparity == 'E') parity = SerialPort.PARITY_EVEN; + if (iparity == 'O') parity = SerialPort.PARITY_ODD; + + this.databits = idatabits; + + stopbits = SerialPort.STOPBITS_1; + if (istopbits == 1.5f) stopbits = SerialPort.STOPBITS_1_5; + if (istopbits == 2) stopbits = SerialPort.STOPBITS_2; + + try { + Enumeration portList = CommPortIdentifier.getPortIdentifiers(); + while (portList.hasMoreElements()) { + CommPortIdentifier portId = + (CommPortIdentifier) portList.nextElement(); + + if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) { + //System.out.println("found " + portId.getName()); + if (portId.getName().equals(iname)) { + port = (SerialPort)portId.open("serial madness", 2000); + input = port.getInputStream(); + output = port.getOutputStream(); + port.setSerialPortParams(rate, databits, stopbits, parity); + port.addEventListener(this); + port.notifyOnDataAvailable(true); + //System.out.println("opening, ready to roll"); + } + } + } + + } catch (Exception e) { + errorMessage("", e); + //exception = e; + //e.printStackTrace(); + port = null; + input = null; + output = null; + } + + parent.registerDispose(this); + + // reflection to check whether host applet has a call for + // public void serialEvent(processing.serial.Serial) + // which would be called each time an event comes in + try { + serialEventMethod = + parent.getClass().getMethod("serialEvent", + new Class[] { Serial.class }); + } catch (Exception e) { + // no such method, or an error.. which is fine, just ignore + } + } + + + public void dispose() { + try { + // do io streams need to be closed first? + if (input != null) input.close(); + if (output != null) output.close(); + + } catch (Exception e) { + e.printStackTrace(); + } + input = null; + output = null; + + try { + if (port != null) port.close(); // close the port + + } catch (Exception e) { + e.printStackTrace(); + } + port = null; + } + + + synchronized public void serialEvent(SerialPortEvent serialEvent) { + if (serialEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) { + try { + while (input.available() > 0) { + synchronized (buffer) { + if (bufferLast == buffer.length) { + byte temp[] = new byte[bufferLast << 1]; + System.arraycopy(buffer, 0, temp, 0, bufferLast); + buffer = temp; + } + buffer[bufferLast++] = (byte) input.read(); + if (serialEventMethod != null) { + if ((bufferUntil && (buffer[bufferLast-1] == bufferUntilByte)) || + ((bufferLast - bufferIndex) >= bufferSize)) { + try { + serialEventMethod.invoke(parent, new Object[] { this }); + } catch (Exception e) { + String msg = "error, disabling serialEvent() for " + port; + System.err.println(msg); + e.printStackTrace(); + serialEventMethod = null; + } + } + } + } + } + + } catch (IOException e) { + errorMessage("serialEvent", e); + } + } + } + + + /** + * Set number of bytes to buffer before calling serialEvent() + * in the host applet. + */ + public void buffer(int count) { + bufferUntil = false; + bufferSize = count; + } + + + /** + * Set a specific byte to buffer until before calling + * serialEvent() in the host applet. + */ + public void bufferUntil(int what) { + bufferUntil = true; + bufferUntilByte = what; + } + + + /** + * Returns the number of bytes that have been read from serial + * and are waiting to be dealt with by the user. + */ + public int available() { + return (bufferLast - bufferIndex); + } + + + /** + * Ignore all the bytes read so far and empty the buffer. + */ + public void clear() { + bufferLast = 0; + bufferIndex = 0; + } + + + /** + * Returns a number between 0 and 255 for the next byte that's + * waiting in the buffer. + * Returns -1 if there was no byte (although the user should + * first check available() to see if things are ready to avoid this) + */ + public int read() { + if (bufferIndex == bufferLast) return -1; + + synchronized (buffer) { + int outgoing = buffer[bufferIndex++] & 0xff; + if (bufferIndex == bufferLast) { // rewind + bufferIndex = 0; + bufferLast = 0; + } + return outgoing; + } + } + + + /** + * Same as read() but returns the very last value received + * and clears the buffer. Useful when you just want the most + * recent value sent over the port. + */ + public int last() { + if (bufferIndex == bufferLast) return -1; + synchronized (buffer) { + int outgoing = buffer[bufferLast-1]; + bufferIndex = 0; + bufferLast = 0; + return outgoing; + } + } + + + /** + * Returns the next byte in the buffer as a char. + * Returns -1, or 0xffff, if nothing is there. + */ + public char readChar() { + if (bufferIndex == bufferLast) return (char)(-1); + return (char) read(); + } + + + /** + * Just like last() and readChar(). + */ + public char lastChar() { + if (bufferIndex == bufferLast) return (char)(-1); + return (char) last(); + } + + + /** + * Return a byte array of anything that's in the serial buffer. + * Not particularly memory/speed efficient, because it creates + * a byte array on each read, but it's easier to use than + * readBytes(byte b[]) (see below). + */ + public byte[] readBytes() { + if (bufferIndex == bufferLast) return null; + + synchronized (buffer) { + int length = bufferLast - bufferIndex; + byte outgoing[] = new byte[length]; + System.arraycopy(buffer, bufferIndex, outgoing, 0, length); + + bufferIndex = 0; // rewind + bufferLast = 0; + return outgoing; + } + } + + + /** + * Grab whatever is in the serial buffer, and stuff it into a + * byte buffer passed in by the user. This is more memory/time + * efficient than readBytes() returning a byte[] array. + * + * Returns an int for how many bytes were read. If more bytes + * are available than can fit into the byte array, only those + * that will fit are read. + */ + public int readBytes(byte outgoing[]) { + if (bufferIndex == bufferLast) return 0; + + synchronized (buffer) { + int length = bufferLast - bufferIndex; + if (length > outgoing.length) length = outgoing.length; + System.arraycopy(buffer, bufferIndex, outgoing, 0, length); + + bufferIndex += length; + if (bufferIndex == bufferLast) { + bufferIndex = 0; // rewind + bufferLast = 0; + } + return length; + } + } + + + /** + * Reads from the serial port into a buffer of bytes up to and + * including a particular character. If the character isn't in + * the serial buffer, then 'null' is returned. + */ + public byte[] readBytesUntil(int interesting) { + if (bufferIndex == bufferLast) return null; + byte what = (byte)interesting; + + synchronized (buffer) { + int found = -1; + for (int k = bufferIndex; k < bufferLast; k++) { + if (buffer[k] == what) { + found = k; + break; + } + } + if (found == -1) return null; + + int length = found - bufferIndex + 1; + byte outgoing[] = new byte[length]; + System.arraycopy(buffer, bufferIndex, outgoing, 0, length); + + bufferIndex = 0; // rewind + bufferLast = 0; + return outgoing; + } + } + + + /** + * Reads from the serial port into a buffer of bytes until a + * particular character. If the character isn't in the serial + * buffer, then 'null' is returned. + * + * If outgoing[] is not big enough, then -1 is returned, + * and an error message is printed on the console. + * If nothing is in the buffer, zero is returned. + * If 'interesting' byte is not in the buffer, then 0 is returned. + */ + public int readBytesUntil(int interesting, byte outgoing[]) { + if (bufferIndex == bufferLast) return 0; + byte what = (byte)interesting; + + synchronized (buffer) { + int found = -1; + for (int k = bufferIndex; k < bufferLast; k++) { + if (buffer[k] == what) { + found = k; + break; + } + } + if (found == -1) return 0; + + int length = found - bufferIndex + 1; + if (length > outgoing.length) { + System.err.println("readBytesUntil() byte buffer is" + + " too small for the " + length + + " bytes up to and including char " + interesting); + return -1; + } + //byte outgoing[] = new byte[length]; + System.arraycopy(buffer, bufferIndex, outgoing, 0, length); + + bufferIndex += length; + if (bufferIndex == bufferLast) { + bufferIndex = 0; // rewind + bufferLast = 0; + } + return length; + } + } + + + /** + * Return whatever has been read from the serial port so far + * as a String. It assumes that the incoming characters are ASCII. + * + * If you want to move Unicode data, you can first convert the + * String to a byte stream in the representation of your choice + * (i.e. UTF8 or two-byte Unicode data), and send it as a byte array. + */ + public String readString() { + if (bufferIndex == bufferLast) return null; + return new String(readBytes()); + } + + + /** + * Combination of readBytesUntil and readString. See caveats in + * each function. Returns null if it still hasn't found what + * you're looking for. + * + * If you want to move Unicode data, you can first convert the + * String to a byte stream in the representation of your choice + * (i.e. UTF8 or two-byte Unicode data), and send it as a byte array. + */ + public String readStringUntil(int interesting) { + byte b[] = readBytesUntil(interesting); + if (b == null) return null; + return new String(b); + } + + + /** + * This will handle both ints, bytes and chars transparently. + */ + public void write(int what) { // will also cover char + try { + output.write(what & 0xff); // for good measure do the & + output.flush(); // hmm, not sure if a good idea + + } catch (Exception e) { // null pointer or serial port dead + errorMessage("write", e); + } + } + + + public void write(byte bytes[]) { + try { + output.write(bytes); + output.flush(); // hmm, not sure if a good idea + + } catch (Exception e) { // null pointer or serial port dead + //errorMessage("write", e); + e.printStackTrace(); + } + } + + + /** + * Write a String to the output. Note that this doesn't account + * for Unicode (two bytes per char), nor will it send UTF8 + * characters.. It assumes that you mean to send a byte buffer + * (most often the case for networking and serial i/o) and + * will only use the bottom 8 bits of each char in the string. + * (Meaning that internally it uses String.getBytes) + * + * If you want to move Unicode data, you can first convert the + * String to a byte stream in the representation of your choice + * (i.e. UTF8 or two-byte Unicode data), and send it as a byte array. + */ + public void write(String what) { + write(what.getBytes()); + } + + + /** + * If this just hangs and never completes on Windows, + * it may be because the DLL doesn't have its exec bit set. + * Why the hell that'd be the case, who knows. + */ + static public String[] list() { + Vector list = new Vector(); + try { + //System.err.println("trying"); + Enumeration portList = CommPortIdentifier.getPortIdentifiers(); + //System.err.println("got port list"); + while (portList.hasMoreElements()) { + CommPortIdentifier portId = + (CommPortIdentifier) portList.nextElement(); + //System.out.println(portId); + + if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) { + String name = portId.getName(); + list.addElement(name); + } + } + + } catch (UnsatisfiedLinkError e) { + //System.err.println("1"); + errorMessage("ports", e); + + } catch (Exception e) { + //System.err.println("2"); + errorMessage("ports", e); + } + //System.err.println("move out"); + String outgoing[] = new String[list.size()]; + list.copyInto(outgoing); + return outgoing; + } + + + /** + * General error reporting, all corraled here just in case + * I think of something slightly more intelligent to do. + */ + static public void errorMessage(String where, Throwable e) { + e.printStackTrace(); + throw new RuntimeException("Error inside Serial." + where + "()"); + } +} + + + /* + class SerialMenuListener implements ItemListener { + //public SerialMenuListener() { } + + public void itemStateChanged(ItemEvent e) { + int count = serialMenu.getItemCount(); + for (int i = 0; i < count; i++) { + ((CheckboxMenuItem)serialMenu.getItem(i)).setState(false); + } + CheckboxMenuItem item = (CheckboxMenuItem)e.getSource(); + item.setState(true); + String name = item.getLabel(); + //System.out.println(item.getLabel()); + PdeBase.properties.put("serial.port", name); + //System.out.println("set to " + get("serial.port")); + } + } + */ + + + /* + protected Vector buildPortList() { + // get list of names for serial ports + // have the default port checked (if present) + Vector list = new Vector(); + + //SerialMenuListener listener = new SerialMenuListener(); + boolean problem = false; + + // if this is failing, it may be because + // lib/javax.comm.properties is missing. + // java is weird about how it searches for java.comm.properties + // so it tends to be very fragile. i.e. quotes in the CLASSPATH + // environment variable will hose things. + try { + //System.out.println("building port list"); + Enumeration portList = CommPortIdentifier.getPortIdentifiers(); + while (portList.hasMoreElements()) { + CommPortIdentifier portId = + (CommPortIdentifier) portList.nextElement(); + //System.out.println(portId); + + if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) { + //if (portId.getName().equals(port)) { + String name = portId.getName(); + //CheckboxMenuItem mi = + //new CheckboxMenuItem(name, name.equals(defaultName)); + + //mi.addItemListener(listener); + //serialMenu.add(mi); + list.addElement(name); + } + } + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + problem = true; + + } catch (Exception e) { + System.out.println("exception building serial menu"); + e.printStackTrace(); + } + + //if (serialMenu.getItemCount() == 0) { + //System.out.println("dimming serial menu"); + //serialMenu.setEnabled(false); + //} + + // only warn them if this is the first time + if (problem && PdeBase.firstTime) { + JOptionPane.showMessageDialog(this, //frame, + "Serial port support not installed.\n" + + "Check the readme for instructions\n" + + "if you need to use the serial port. ", + "Serial Port Warning", + JOptionPane.WARNING_MESSAGE); + } + return list; + } + */ + + + diff --git a/serial/macosx_setup.command b/serial/macosx_setup.command new file mode 100755 index 000000000..b5d93eba7 --- /dev/null +++ b/serial/macosx_setup.command @@ -0,0 +1,51 @@ +#!/bin/sh + +# originally fixperm.sh from rxtx-2.1_6/contrib +# with a couple additions from pathsetup.command from fink [fry] + +# A script to fix permissions for lock files on Mac OS X +# Contributed by Dmitry Markman +# Fri Aug 23 15:46:46 MDT 2002 + +echo "" +echo "" +echo "This command will take care of a couple system things" +echo "so that you can properly use the serial port to communicate" +echo "between hardware devices and processing." +echo "" +echo "If there are actually changes that need to be made, then" +echo "enter your password when prompted. The command uses sudo," +echo "and you'll need to have administrator access." +echo "" + +echo -n "Do you want to continue? [Y/n] " +read answer +answer=`echo $answer | sed 's/^[yY].*$/y/'` +if [ -z "$answer" -o "x$answer" = "xy" ]; then + curruser=`sudo id -p | grep 'login' | sed 's/login.//'` + + if [ ! -d /var/spool/uucp ] + then + sudo mkdir /var/spool/uucp + fi + + sudo chgrp uucp /var/spool/uucp + sudo chmod 775 /var/spool/uucp + + if [ ! `sudo niutil -readprop / /groups/uucp users | grep $curruser > /dev/null` ] + then + sudo niutil -mergeprop / /groups/uucp users $curruser + fi + + echo "Finished making changes, you should be all set" +else + echo "Ok, nevermind then." +fi + +echo "" +echo "" +echo " (All done... you can close this window now)" +echo "" +echo "" +echo "" + diff --git a/todo.txt b/todo.txt index 668661405..3477b4859 100644 --- a/todo.txt +++ b/todo.txt @@ -26,13 +26,21 @@ X significantly limit range of cases.. if presenting ext is ok X uses internal pretty much only when single class, etc X remove initialWidth/Height stuff from PdeRuntime for when running internally + +monday evening + _ when centering applet on-screen, needs to check for multiple screens _ this is both for PApplet and PdeRuntime _ PApplet screen size constant is no good +_ ability to select monitor via preferences panel _ check current present code with multiple monitors _ if it's working, make it all reflection-based in PApplet +_ first pass on full screen +_ exceptions in full screen mode will quit the app completely +_ (can't keep window open because things are hosed) + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -54,10 +62,6 @@ _ and queue events by registering for draw or whatever they'd like _ lib could call queueEvent with the args _ then call them inside post() -_ first pass on full screen -_ exceptions in full screen mode will quit the app completely -_ (can't keep window open because things are hosed) - _ update checker (can be turned off in prefs) _ send unique id, and information about person's java vm/platform _ using timezone would be an interesting method for tracking location @@ -110,6 +114,8 @@ can tell, is to Save As once, make at least one change, then Save. what happens if folder already exists on save as? +saving a project over an already existing project does not get rid of the .pde files that arent overwritten. not sure if this is feature or bug, but it took me by surprise. it seemed to only overwrite .pde files with the same name, but everything else in that project folder stays as is. + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . diff --git a/video/Camera.java b/video/Camera.java new file mode 100644 index 000000000..72348e1aa --- /dev/null +++ b/video/Camera.java @@ -0,0 +1,473 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Camera - whatchin shit on tv + Part of the Processing project - http://processing.org + + Copyright (c) 2004-05 Ben Fry and Casey Reas + The previous version of this code was developed by Hernando Barragan + + 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.video; +import processing.core.*; + +import java.lang.reflect.*; + +import quicktime.*; +import quicktime.qd.*; +import quicktime.std.*; +import quicktime.std.sg.*; +import quicktime.util.RawEncodedImage; + + +public class Camera extends PImage +implements StdQTConstants, StdQTConstants4, Runnable { + PApplet parent; + Method cameraEventMethod; + String name; // keep track for error messages + Thread runner; + + //PImage borderImage; + //boolean removeBorders = true; + boolean available = false; + + /** Temporary storage for the raw image data read directly from the camera */ + public int data[]; + + public int dataWidth; + public int dataHeight; + public int dataRowBytes; + + public boolean crop; + public int cropX; + public int cropY; + public int cropW; + public int cropH; + + int fps; + RawEncodedImage raw; + SequenceGrabber capture; + + static { + //System.out.println("Camera init"); + try { + QTSession.open(); + } catch (QTException e) { + e.printStackTrace(); + } + QTRuntimeException.registerHandler(new QTRuntimeHandler() { + public void exceptionOccurred(QTRuntimeException e, + Object obj, String s, boolean flag) { + System.err.println("Problem inside Camera"); + e.printStackTrace(); + } + }); + } + + + public Camera(PApplet parent, int requestWidth, int requestHeight) { + this(parent, "", requestWidth, requestHeight, 30); + } + + public Camera(PApplet parent, int reqWidth, int reqHeight, int fps) { + this(parent, "", reqWidth, reqHeight, fps); + } + + public Camera(PApplet parent, String name, int reqWidth, int reqHeight) { + this(parent, name, reqWidth, reqHeight, 30); + } + + + /* + quicktime.QTSession.open(); + quicktime.std.sg.SequenceGrabber sg = new quicktime.std.sg.SequenceGrabber(); + quicktime.std.sg.SGVideoChannel sc = new quicktime.std.sg.SGVideoChannel(sg); + quicktime.std.sg.VideoDigitizer vd = sc.getDigitizerComponent(); + println( "dv.getNumberOfInputs :" ); println( vd.getNumberOfInputs() ); println(); +//change line below to set input source + vd.setInput(1); +// + println( "dv.getInput :" ); println( vd.getInput() ); println(); + } catch (Exception e) { + e.printStackTrace(); + } + */ + + /** + * If 'name' is the empty string, don't set a specific device, + * which means that QuickTime will use that last device used by + * a QuickTime application. If name is set to null, a prompt + * will show up, allowing the user to choose the good stuff. + *

+ * If the following function: + * public void cameraEvent(Camera c) + * is defined int the host PApplet, then it will be called every + * time a new frame is available from the camera. + */ + public Camera(PApplet parent, String name, + int requestWidth, int requestHeight, int fps) { + this.parent = parent; + this.name = name; + this.fps = fps; + + try { + /* + QTSession.open(); + QTRuntimeException.registerHandler(new QTRuntimeHandler() { + public void exceptionOccurred(QTRuntimeException e, + Object obj, String s, boolean flag) { + System.err.println("Problem inside Camera"); + e.printStackTrace(); + } + }); + */ + //System.out.println("0"); + + QDRect qdrect = new QDRect(requestWidth, requestHeight); + QDGraphics qdgraphics = new QDGraphics(qdrect); + + //System.out.println("0a"); + + capture = new SequenceGrabber(); + //System.out.println("0b"); + capture.setGWorld(qdgraphics, null); + + //System.out.println("0c"); + + // CRASHING HERE ON OSX + SGVideoChannel channel = new SGVideoChannel(capture); + //System.out.println("0c1"); + channel.setBounds(qdrect); + //System.out.println("0c2"); + channel.setUsage(2); // what is this usage number? + //System.out.println("0c3"); + capture.startPreview(); // maybe this comes later? + + //System.out.println("0d"); + + PixMap pixmap = qdgraphics.getPixMap(); + raw = pixmap.getPixelData(); + + //System.out.println("0e"); + + if (name == null) { + channel.settingsDialog(); + + } else if (name.length() > 0) { + channel.setDevice(name); + } + //System.out.println("0f"); + //channel.setDevice("Logitech QuickCam Express-WDM"); + /* + if (showDialog) channel.settingsDialog(); + SGDeviceList list = channel.getDeviceList(0); + System.out.println("count is " + list.getCount()); + for (int i = 0; i < list.getCount(); i++) { + System.out.println(list.getDeviceName(i).getName()); + } + //System.out.println(channel.getSettings()); + */ + + //int grabWidthBytes = raw.getRowBytes(); + dataRowBytes = raw.getRowBytes(); + dataWidth = dataRowBytes / 4; + + //System.out.println("row bytes " + raw.getRowBytes() + " " + + // (raw.getRowBytes() / 4)); + //int extraBytes = dataRowBytes - requestWidth*4; + //int extraPixels = extraBytes / 4; + //int videoWidth = requestWidth + extraPixels; + dataHeight = raw.getSize() / dataRowBytes; + + //System.out.println("height req, actual: " + requestHeight + + // " " + dataHeight); + + if (dataWidth != requestWidth) { + crop = true; + cropX = 0; + cropY = 0; + cropW = requestWidth; + cropH = requestHeight; + + /* + System.out.println("dataWidth is " + dataWidth + + " not " + requestWidth); + if (removeBorders) { + int bpixels[] = new int[dataWidth * requestHeight]; + borderImage = new PImage(bpixels, dataWidth, requestHeight, RGB); + } else { + requestWidth = dataWidth; + } + */ + } + // initialize my PImage self + super.init(requestWidth, requestHeight, RGB); + + runner = new Thread(this); + runner.start(); + + parent.registerDispose(this); + + try { + cameraEventMethod = + parent.getClass().getMethod("cameraEvent", + new Class[] { Camera.class }); + } catch (Exception e) { + // no such method, or an error.. which is fine, just ignore + //e.printStackTrace(); + } + + } catch (StdQTException qte) { + //qte.printStackTrace(); + + int errorCode = qte.errorCode(); + // where's the friggin constant for this? + if (errorCode == -9405) { + // this can happen when the camera isn't available or + // wasn't shut down properly + parent.die("The camera (or VDIG) is not " + + "installed correctly (see readme.txt).", qte); + } else { + parent.die("Error while setting up Camera", qte); + } + } catch (Exception e) { + parent.die("Error while setting up Camera", e); + } + } + + + /** + * True if a frame is ready to be read. Example: + * if (camera.available()) camera.read(); + *

+ * Alternatively, you can use cameraEvent(Camera c) to notify you + * whenever available() is set to true. + */ + public boolean available() { + return available; + } + + + /** + * Set the video to crop from its original. + *

+ * It seems common that cameras add lines to the top or bottom + * of an image, so this can be useful for removing them. + * Internally, the pixel buffer size returned from QuickTime is + * often a different size than requested, so crop will be set + * more often than not. + */ + public void crop(int x, int y, int w, int h) { + if (imageMode == CORNERS) { + w -= x; // w was actually x2 + h -= y; // h was actually y2 + } + + crop = true; + cropX = Math.max(0, x); + cropY = Math.max(0, y); + cropW = Math.min(w, dataWidth); + cropH = Math.min(dataHeight, y + h) - cropY; + + // if size has changed, re-init this image + if ((cropW != width) || (cropH != height)) { + init(w, h, RGB); + } + } + + + public void noCrop() { + crop = false; + } + + + public void read() { + //try { + //synchronized (capture) { + synchronized (pixels) { + //long t1 = System.currentTimeMillis(); + + if (crop) { + /* + // f#$)(#$ing quicktime / jni is so g-d slow that this + // code takes literally 100x longer to run + int sourceOffset = cropX*4 + cropY*dataRowBytes; + int destOffset = 0; + for (int y = 0; y < cropH; y++) { + raw.copyToArray(sourceOffset, pixels, destOffset, cropW); + sourceOffset += dataRowBytes; + destOffset += width; + } + */ + if (data == null) { + data = new int[dataWidth * dataHeight]; + } + raw.copyToArray(0, data, 0, dataWidth * dataHeight); + int sourceOffset = cropX + cropY*dataWidth; + int destOffset = 0; + for (int y = 0; y < cropH; y++) { + System.arraycopy(data, sourceOffset, pixels, destOffset, cropW); + sourceOffset += dataWidth; + destOffset += width; + } + + /* + if (borderImage != null) { // need to remove borders + raw.copyToArray(0, borderImage.pixels, + 0, borderImage.width * borderImage.height); + int borderIndex = 0; + int targetIndex = 0; + for (int i = 0; i < height; i++) { + System.arraycopy(borderImage.pixels, borderIndex, + pixels, targetIndex, width); + borderIndex += borderImage.width; + targetIndex += width; + } + */ + } else { // just copy directly + raw.copyToArray(0, pixels, 0, width * height); + } + //long t2 = System.currentTimeMillis(); + //System.out.println(t2 - t1); + + available = false; + // mark this image as modified so that PGraphics2 and PGraphicsGL + // willproperly re-blit and draw this guy + updatePixels(); + } + } + + + public void run() { + while ((Thread.currentThread() == runner) && (capture != null)) { + try { + synchronized (capture) { + capture.idle(); + //read(); + available = true; + + if (cameraEventMethod != null) { + try { + cameraEventMethod.invoke(parent, new Object[] { this }); + } catch (Exception e) { + System.err.println("Disabling cameraEvent() for " + name + + " because of an error."); + e.printStackTrace(); + cameraEventMethod = null; + } + } + } + + } catch (QTException e) { + errorMessage("run", e); + } + + try { + Thread.sleep(1000 / fps); + } catch (InterruptedException e) { } + } + } + + + /** + * Set the framerate for how quickly new frames are read + * from the camera. + */ + public void framerate(int ifps) { + if (ifps <= 0) { + System.err.println("Camera: ignoring bad framerate of " + + ifps + " fps."); + return; + } + fps = ifps; + } + + + /** + * Called by applets to stop capturing video. + */ + public void stop() { + if (capture != null) { + try { + capture.stop(); // stop the "preview" + } catch (StdQTException e) { + e.printStackTrace(); + } + capture = null; + } + runner = null; // unwind the thread + } + + + /** + * Called by PApplet to shut down video so that QuickTime + * can be used later by another applet. + */ + public void dispose() { + stop(); + //System.out.println("calling dispose"); + // this is important so that the next app can do video + QTSession.close(); + } + + + /** + * General error reporting, all corraled here just in case + * I think of something slightly more intelligent to do. + */ + protected void errorMessage(String where, Exception e) { + parent.die("Error inside Camera." + where + "()", e); + } + + + /** + * Shows the settings dialog for this channel. + */ + //public void prompt() { + //channel.settingsDialog(); + //} + + + /** + * Get a list of all available cameras as a String array. + * i.e. printarr(Camera.list()) will show you the goodies. + */ + static public String[] list() { + try { + SequenceGrabber grabber = new SequenceGrabber(); + SGVideoChannel channel = new SGVideoChannel(grabber); + + //VideoDigitizer digitizer = channel.getDigitizerComponent(); + //digitizer.setInput(2); // or something + //DigitizerInfo di = digitizer.getDigitizerInfo() + + SGDeviceList deviceList = channel.getDeviceList(0); // flags is 0 + String listing[] = new String[deviceList.getCount()]; + for (int i = 0; i < deviceList.getCount(); i++) { + listing[i] = deviceList.getDeviceName(i).getName(); + } + // properly shut down the channel so the app can use it again + grabber.disposeChannel(channel); + return listing; + + } catch (QTException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/video/Movie.java b/video/Movie.java new file mode 100644 index 000000000..0b6c92b03 --- /dev/null +++ b/video/Movie.java @@ -0,0 +1,571 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + PMovie - reading from video files + Part of the Processing project - http://processing.org + + Copyright (c) 2004 Ben Fry + The previous version of this code was developed by Hernando Barragan + + 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.video; +import processing.core.*; + +import java.awt.event.*; +import java.io.*; +import java.lang.reflect.*; +import java.net.*; + +import quicktime.*; +import quicktime.io.QTFile; +import quicktime.qd.*; +import quicktime.std.*; +import quicktime.std.movies.*; +import quicktime.std.movies.media.DataRef; +import quicktime.util.RawEncodedImage; + + +public class Movie extends PImage +implements StdQTConstants, StdQTConstants4, PConstants, Runnable { + PApplet parent; + Method movieEventMethod; + String filename; + Thread runner; + + PImage borderImage; + boolean removeBorders = true; + + boolean play; + boolean repeat; + boolean available; + //float rate; + int fps; + + /** The movie object, made public in case anyone wants to play with it. */ + public quicktime.std.movies.Movie movie; + + QDRect movieRect; + QDGraphics movieGraphics; + boolean firstFrame = true; + RawEncodedImage raw; + + + static { + try { + //System.out.println("jlp = " + System.getProperty("java.library.path")); + QTSession.open(); + } catch (QTException e) { + e.printStackTrace(); + } + QTRuntimeException.registerHandler(new QTRuntimeHandler() { + public void exceptionOccurred(QTRuntimeException e, + Object obj, String s, boolean flag) { + System.err.println("Problem inside Movie"); + e.printStackTrace(); + } + }); + } + + + public Movie(PApplet parent, String filename) { + this(parent, filename, 30); + } + + + public Movie(PApplet parent, String filename, int ifps) { + //this(parent, parent.getClass().getResource("data/" + filename), ifps); + + // this creates a fake image so that the first time this + // attempts to draw, something happens that's not an exception + super.init(1, 1, RGB); + + URL url = null; + this.filename = filename; // for error messages + + if (filename.startsWith("http://")) { + try { + url = new URL(filename); + DataRef urlRef = new DataRef(url.toExternalForm()); + movie = fromDataRef(urlRef); + //movie = quicktime.std.movies.Movie.fromDataRef(urlRef, + //StdQTConstants4.newMovieAsyncOK | + //StdQTConstants.newMovieActive); + init(parent, movie, ifps); + return; + + } catch (QTException qte) { + qte.printStackTrace(); + return; + + } catch (MalformedURLException e) { + e.printStackTrace(); + return; + } + } + + url = getClass().getResource(filename); + if (url != null) { + init(parent, url, ifps); + return; + } + + url = getClass().getResource("data/" + filename); + if (url != null) { + init(parent, url, ifps); + return; + } + + try { + try { + // look inside the sketch folder (if set) + String location = parent.folder + File.separator + "data"; + File file = new File(location, filename); + if (file.exists()) { + movie = fromDataRef(new DataRef(new QTFile(file))); + //movie = quicktime.std.movies.Movie.fromDataRef(new DataRef(new QTFile(file)), + //StdQTConstants4.newMovieAsyncOK | + //StdQTConstants.newMovieActive); + init(parent, movie, ifps); + return; + } + } catch (QTException e) { } // ignored + + try { + File file = new File("data", filename); + if (file.exists()) { + movie = fromDataRef(new DataRef(new QTFile(file))); + //movie = quicktime.std.movies.Movie.fromDataRef(new DataRef(new QTFile(file)), + //StdQTConstants4.newMovieAsyncOK | + //StdQTConstants.newMovieActive); + init(parent, movie, ifps); + return; + } + } catch (QTException e2) { } + + try { + File file = new File(filename); + if (file.exists()) { + movie = fromDataRef(new DataRef(new QTFile(file))); + //movie = quicktime.std.movies.Movie.fromDataRef(new DataRef(new QTFile(file)), + //StdQTConstants4.newMovieAsyncOK | + //StdQTConstants.newMovieActive); + init(parent, movie, ifps); + return; + } + } catch (QTException e1) { } + + } catch (SecurityException se) { } // online, whups + + parent.die("Could not find movie file " + filename, null); + } + + + public Movie(PApplet parent, URL url) { + init(parent, url, 30); + } + + + public Movie(PApplet parent, URL url, int ifps) { + init(parent, url, ifps); + } + + + public void init(PApplet parent, URL url, int ifps) { + + // qtjava likes file: urls to read file:/// not file:/ + // so this changes them when appropriate + String externalized = url.toExternalForm(); + if (externalized.startsWith("file:/") && + !externalized.startsWith("file:///")) { + externalized = "file:///" + url.getPath(); + } + + // the url version is the only available that can take + // an InputStream (indirectly) since it uses url syntax + //DataRef urlRef = new DataRef(requestFile); + try { + DataRef urlRef = new DataRef(externalized); + movie = fromDataRef(urlRef); + //movie = quicktime.std.movies.Movie.fromDataRef(urlRef, + //StdQTConstants4.newMovieAsyncOK | + //StdQTConstants.newMovieActive); + init(parent, movie, ifps); + + } catch (QTException e) { + e.printStackTrace(); + } + //System.out.println("done with init"); + } + + + private quicktime.std.movies.Movie fromDataRef(DataRef ref) + throws QTException { + + return + quicktime.std.movies.Movie.fromDataRef(ref, + StdQTConstants4.newMovieAsyncOK | + StdQTConstants.newMovieActive); + } + + + public void init(PApplet parent, + quicktime.std.movies.Movie movie, int ifps) { + this.parent = parent; + + try { + movie.prePreroll(0, 1.0f); + movie.preroll(0, 1.0f); + while (movie.maxLoadedTimeInMovie() == 0) { + movie.task(100); + } + movie.setRate(1); + fps = ifps; + + runner = new Thread(this); + runner.start(); + + + // register methods + + parent.registerDispose(this); + + try { + movieEventMethod = + parent.getClass().getMethod("movieEvent", + new Class[] { Movie.class }); + } catch (Exception e) { + // no such method, or an error.. which is fine, just ignore + } + + } catch (QTException qte) { + qte.printStackTrace(); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + + public boolean available() { + return available; + } + + + public void read() { + try { + if (firstFrame) { + movieRect = movie.getBox(); + movieGraphics = new QDGraphics(movieRect); + } + + Pict pict = movie.getPict(movie.getTime()); // returns an int + pict.draw(movieGraphics, movieRect); + PixMap pixmap = movieGraphics.getPixMap(); + raw = pixmap.getPixelData(); + + // It needs to get at least a small part + // of the video to get the parameters + if (firstFrame) { + //int intsPerRow = pixmap.getRowBytes() / 4; + int movieWidth = movieRect.getWidth(); + int movieHeight = movieRect.getHeight(); + int j = raw.getRowBytes() - movieWidth*4; + // this doesn't round up.. does it need to? + int k = j / 4; + int dataWidth = movieWidth + k; + + if (dataWidth != movieWidth) { + if (removeBorders) { + int bpixels[] = new int[dataWidth * movieHeight]; + borderImage = new PImage(bpixels, dataWidth, movieHeight, RGB); + } else { + movieWidth = dataWidth; + } + } + int vpixels[] = new int[movieWidth * movieHeight]; + //image = new PImage(vpixels, movieWidth, movieHeight, RGB); + super.init(movieWidth, movieHeight, RGB); + //parent.video = image; + firstFrame = false; + } + // this happens later (found by hernando) + //raw.copyToArray(0, image.pixels, 0, image.width * image.height); + + // this is identical to a chunk of code inside PCamera + // this might be a candidate to move up to PVideo or something + if (borderImage != null) { // need to remove borders + raw.copyToArray(0, borderImage.pixels, + 0, borderImage.width * borderImage.height); + int borderIndex = 0; + int targetIndex = 0; + for (int i = 0; i < height; i++) { + System.arraycopy(borderImage.pixels, borderIndex, + pixels, targetIndex, width); + borderIndex += borderImage.width; + targetIndex += width; + } + } else { // just copy directly + raw.copyToArray(0, pixels, 0, width * height); + } + + // ready to rock + //System.out.println("updating pixels"); + updatePixels(); // mark as modified + + } catch (QTException qte) { + qte.printStackTrace(); + QTSession.close(); + } + } + + + /** + * Begin playing the movie, with no repeat. + */ + public void play() { + play = true; + } + + + /** + * Begin playing the movie, with repeat. + */ + public void loop() { + play = true; + repeat = true; + } + + + /** + * Shut off the repeating loop. + */ + public void noLoop() { + repeat = false; + } + + + /** + * Pause the movie at its current time. + */ + public void pause() { + play = false; + System.out.println("pause"); + } + + + /** + * Stop the movie, and rewind. + */ + public void stop() { + play = false; + System.out.println("stop"); + try { + movie.setTimeValue(0); + + } catch (StdQTException e) { + errorMessage("stop", e); + } + } + + + /** + * Set how often new frames are to be read from the movie. + */ + public void framerate(int ifps) { + if (ifps <= 0) { + System.err.println("Movie: ignoring bad framerate of " + + ifps + " fps."); + return; + } + fps = ifps; + } + + + /** + * Set a multiplier for how fast/slow the movie should be run. + * speed(2) will play the movie at double speed (2x). + * speed(0.5) will play at half speed. + * speed(-1) will play backwards at regular speed. + */ + public void speed(float rate) { + //rate = irate; + try { + movie.setRate(rate); + + } catch (StdQTException e) { + errorMessage("speed", e); + } + } + + + /** + * Return the current time in seconds. + * The number is a float so fractions of seconds can be used. + */ + public float time() { + try { + return (float)movie.getTime() / (float)movie.getTimeScale(); + + } catch (StdQTException e) { + errorMessage("time", e); + } + return -1; + } + + + /** + * Jump to a specific location (in seconds). + * The number is a float so fractions of seconds can be used. + */ + //public void jump(int where) { + public void jump(float where) { + try { + //movie.setTime(new TimeRecord(rate, where)); // scale, value + //movie.setTime(new TimeRecord(1, where)); // scale, value + int scaledTime = (int) (where * movie.getTimeScale()); + movie.setTimeValue(scaledTime); + + } catch (StdQTException e) { + errorMessage("jump", e); + } + } + + + /** + * Get the full length of this movie (in seconds). + */ + public float duration() { + try { + return (float)movie.getDuration() / (float)movie.getTimeScale(); + + } catch (StdQTException e) { + errorMessage("length", e); + } + return -1; + } + + + /* + public void play() { + if(!play) { + play = true; + } + start(); + while( image == null) { + try { + Thread.sleep(5); + } catch (InterruptedException e) { } + } + pixels = image.pixels; + width = image.width; + height = image.height; + } + + + public void repeat() { + loop = true; + if(!play) { + play = true; + } + start(); + while( image == null) { + try { + Thread.sleep(5); + } catch (InterruptedException e) { } + } + pixels = image.pixels; + width = image.width; + height = image.height; + } + + + public void pause() { + play = false; + } + */ + + + public void run() { + //System.out.println("entering thread"); + while (Thread.currentThread() == runner) { + //System.out.print("<"); + try { + //Thread.sleep(5); + Thread.sleep(1000 / fps); + } catch (InterruptedException e) { } + //System.out.print(">"); + + // this could be a lie, but.. + if (play) { + //read(); + //System.out.println("play"); + available = true; + + if (movieEventMethod != null) { + try { + movieEventMethod.invoke(parent, new Object[] { this }); + } catch (Exception e) { + System.err.println("error, disabling movieEvent() for " + + filename); + e.printStackTrace(); + movieEventMethod = null; + } + } + + try { + if (movie.isDone() && repeat) { + movie.goToBeginning(); + } + } catch (StdQTException e) { + play = false; + errorMessage("rewinding", e); + } + //} else { + //System.out.println("no play"); + } + + //try { + //read(); + //if (movie.isDone() && loop) movie.goToBeginning(); + + //} catch (QTException e) { + //System.err.println("Movie exception"); + //e.printStackTrace(); + //QTSession.close(); ?? + //} + } + } + + + public void dispose() { + System.out.println("disposing"); + stop(); + runner = null; + QTSession.close(); + } + + + /** + * General error reporting, all corraled here just in case + * I think of something slightly more intelligent to do. + */ + protected void errorMessage(String where, Exception e) { + parent.die("Error inside Movie." + where + "()", e); + } +} + diff --git a/video/library/.cvsignore b/video/library/.cvsignore new file mode 100644 index 000000000..beaeae295 --- /dev/null +++ b/video/library/.cvsignore @@ -0,0 +1 @@ +video.jar \ No newline at end of file