Monday 22 June 2009

Using Processing in NetBeans (6.5)

Boyd mentioned passing a reference to the current PApplet to separate classes from a Processing app. For anyone who has not yet tried combining Processing and NetBeans, points I have noted are here - there may be better alternatives. This post shows an example that implements a clipboard Copy and Paste subsystem. The code can be used in any processing application. Three variants are discussed: implementation as an inner class in a NetBeans (or PDE) environment, as a separate class in NetBeans and, using the resulting .jar from the Processing IDE (PDE). Initially a NetBeans Processing application is needed.

Creating a NetBeans Processing app.

1) In NetBeans make a New project, say 'Processing Template', with Create Main Class say 'processing.ProcessingApp'. (if you change the package name in the wizard, the package is created OK but the package processing; statement may be missing from the listing: just type it in)

2) Right click on the Libraries node in the project tree: Add JAR/folder, navigate to the folder where Processing core.jar is located, ...\processing-1.0.2-expert\ processing-1.0.2-expert\lib on my system. Select 'core.jar', and Open.

3) Modify the class ProcessingApp as shown:


package processing;

import processing.core.*;

/**
*
* @author Martin Humby
*/
public class ProcessingApp extends PApplet {

public static void main(String[] args) {
PApplet.main(new String[]{"processing.ProcessingApp"});
}

// Code that can optionally be copied to and compiled from the PDE goes here.
// The PDE only requires explicit import of packages it does not import
// implicitly: import java.awt.datatransfer and java.awt.image for the example
// below. Duplicating implicit imports is OK, for the standard imports
// that is. The PDE does not like public void setup() { } - delete public

} // end of ProcessingApp

Note that the string literal in main() must match the package name and class name. If you use Copy, Paste - Refactor to get a version copy and want to be able to run the copy, refactor does not modify the string and the new package and/or class-name must be changed by hand (otherwise the copy runs the current version of the original - confusing!).

To get a slightly more convenient way of accessing the library path to core.jar, select Tools - Libraries from the NetBeans menu bar. New Library, Library Name: 'Processing', Library Type: Class Libraries. Make sure Processing is selected in the Libraries tree and do Add JAR/Folder as in 2) above. Now right click the Libraries node for the project select Add Library. Scroll down the list of Global Libraries and select Processing.

Making the outline shown above a module template or plugin that can be installed into the IDE appears to have disadvantages. Seems to me the easiest way to keep a framework for Processing apps. handy is simply to save the outline as a standard project. Open the outline and use right-click Copy from the project node to get and rename a new project complete with the core.jar path already set up. You can of course include other Processing libraries with the outline.

Passing an explicit PApplet

An inner class that might be factored off to a separate class is a Clipboard Copy and Paste subsystem. Hiding its implementation means passing the current PApplet instance to its Copy and Paste functions. Writing subsystem access methods and the inner class as static ensures a separate class can be got with minimum modifications. Here the static keyword excludes passing an implicit this reference to the inner class: same situation as in a separate class. A difference is that enclosing class static fields remain accessible without qualification. Need to put this in if a straight transfer is going to work, (g.format == PApplet.ARGB) for example, or add such qualifications later.

To try this approach Copy the 'Processing Template' as a new project say 'Processing Clipboard' and modify the class ProcessingApp as shown. You can of course use Refactor to rename the class if you wish and modify the string in main() to suit.

package processing;

import java.awt.*;
import processing.core.*;
import java.awt.datatransfer.*;
import java.awt.image.BufferedImage;
import java.io.IOException;

/**
*
* @author Martin Humby
*/
public class ProcessingApp extends PApplet {

public static void main(String[] args) {
PApplet.main(new String[]{"processing.ProcessingApp"});
}

static final int WIDTH = 500;
static final int PANEL_HT = 160;
static final int HEIGHT = PANEL_HT + 500;
static final int DRAWING_HEIGHT = HEIGHT - PANEL_HT;
static final int PANEL_TOP = DRAWING_HEIGHT;

public void setup() {
size(WIDTH, HEIGHT);
background(255);

// include a panel below drawing area
fill(128);
rect(0, PANEL_TOP, WIDTH - 1, PANEL_HT - 1);
}

// draw() is required to get keyboard monitoring apparently
public void draw() {
}

public void keyReleased() {
// CTRL+C: Copy to Clipboard
if (key == 3) {
copyImageToClipboard(0, 0, width, height - PANEL_HT, this);
}

// CTRL+V: Paste Clipboard
if (key == 22) {
PImage img = getClipboardImage(this);
if (img == null)
return; // not an image on clipboard

// blank drawing area if required
background(255);

// paste image centred on area above panel
int w = img.width;
int h = img.height;
int dx = (width - w) / 2;
int dy = (height - PANEL_HT - h) / 2;
image(img, dx, dy);

// restore the panel area
fill(128);
rect(0, PANEL_TOP, WIDTH - 1, PANEL_HT - 1);
}
}

/////////////////////////////////////////////////////////////////////////////
//
// COPY AND PASTE SUBSYSTEM
//

/**
* Clipboard copy and paste: the following references were used
* http://processing.org/hacks/hacks:clipboard
* http://www.devx.com/Java/Article/22326/0/page/1 et seq.
*
*/

public static void copyImageToClipboard(int top, int left, int width,
int height, PApplet p) {

// could pass this.g directly but passing this for both functions gets
// consistency in use
PGraphics g = p.g;

// g is needed to get format
BufferedImage bImage = new BufferedImage(width, height,
(g.format == PApplet.ARGB) ?
BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
g.loadPixels();

// some explicit identifiers clarify setRGB() usage:
// setRGB(bImageX, bImageY, bothW, bothH, source[], srcOffset, sourceW);
bImage.setRGB(0, 0, width, height, g.pixels, left + top * g.width, g.width);

TransferablePImage imageSelection = new TransferablePImage(bImage);
Toolkit.getDefaultToolkit().
getSystemClipboard().setContents(imageSelection, null);
}

// return a PImage rather than an awt.Image
public static PImage getClipboardImage(PApplet p) {

Transferable t =
Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
try {
if (t != null && t.isDataFlavorSupported(DataFlavor.imageFlavor)) {
BufferedImage bimg = (BufferedImage)
t.getTransferData(DataFlavor.imageFlavor);
int w = bimg.getWidth();
int h = bimg.getHeight();

// make it ARGB to be on the safe side - makes no difference
// unless clipboard image has some tranparency could be?
PImage pImg = p.createImage(w, h, PApplet.ARGB);
pImg.loadPixels();
bimg.getRGB(0, 0, w, h, pImg.pixels, 0, w);
pImg.updatePixels();
return pImg;
}
} catch (UnsupportedFlavorException e) {
} catch (IOException e) {
}
return null;
}

static class TransferablePImage implements Transferable {

// the class-name makes more sense as a separate class
private Image image; // an awt.Image

public TransferablePImage(Image image) {
this.image = image;
}

public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException {
if (!flavor.equals(DataFlavor.imageFlavor)) {
throw new UnsupportedFlavorException(flavor);
}
return image;
}

public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.equals(DataFlavor.imageFlavor);
}

public DataFlavor[] getTransferDataFlavors() {
// in some cases there may be more than one flavour?
return new DataFlavor[]{DataFlavor.imageFlavor};
}
}
} // end of ProcessingApp

To test it open say Paint and load an image. Copy a selection smaller than 500 x 500, and paste to the Processing app. Copy and paste it back to Paint - the white border now surrounds the pasted area.


Factoring off the Clipboard Subsystem

It is optional whether a separate TransferablePImage class is put in a new package but doing this will make life easier if you want to use it from the Processing IDE. Right click Source Packages - New - Java Package, 'utilities' then
utilities - New - Java Class - 'TransferablePImage'.

Copy the inner class to the new file and modify it copying the subsystem interface methods as static class methods:

package utilities;

import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import processing.core.*;

/**
* Copy or paste Processing PImage to / from system clipboard
*
* @author Martin Humby
*/
public class TransferablePImage implements Transferable{

private Image image;

public TransferablePImage(Image image) {
this.image = image;
}

// static methods now part of class

public static void copyImageToClipboard(int top, int left, int width,
int height, PApplet p) {

... body as inner class

}

public static PImage getClipboardImage(PApplet p) {

... body as inner class

}

... other methods as inner class
}


To test the new class make a copy of ProcessingApp, say ProcessingApp1, using copy and paste to the processing package. Do not forget to change the class name in the string literal! Add import utilities.TransferablePImage; Delete the COPY AND PASTE SUBSYSTEM. Modify lines that call Copy and Paste to:

TransferablePImage.copyImageToClipboard(0, 0, width, height - PANEL_HT, this);

PImage img = TransferablePImage.getClipboardImage(this);

Optionally delete ProcessingApp1 imports that are no longer used and proceed as before.


Using TransferablePImage from the PDE

Create a new NetBeans project 'processing utilities' with Create Main Class unchecked. Fix up the libraries path to core.jar. Copy and Paste the utilities package from its current location to the new project's Source Packages node. Build the project: the location of the resulting processing_utilities.jar file is shown in the NetBeans Output window.

Make a new folder called maybe 'jars'. A good place could be in the default My Docments\Processing folder. Copy the processing_utilities.jar file to the new folder. Open the Processing IDE and make a new Sketch. Copy in the code from ProcessingApp1.java from after the main() method. Do not include the last curly bracket that belongs to the enclosing class. Delete public from the setup() method.

From the PDE menu bar select Sketch - Add File... navigate to the 'jars' folder and open processing_utilities.jar. The IDE reports 'One file added to the sketch.' No import statement is required and putting one in gets a compilation error. Run the sketch - the result should be identical to the NetBeans version. You can of course include several other classes in processing_utilities.

For more on this subject see http://dev.processing.org/libraries/


Summary

Most code that compiles in the Processing IDE will compile in NetBeans with a PApplet wrapper and fixed up imports. There are a few exceptions. The non-Java color data type is an integer and needs changing to int. A second minor but annoying problem is the fact that Java has the default type for floating point as double. Processing makes the type implied but overflow checked on compile (neither check for values effectively zero apparently).

Processing: float h = 4.13566733e-15; Java: float h = 4.13566733e-15f; Compiler simplicity maybe but after inserting a few dozen 'f's you begin to wonder whether it generates float g = 42; as a promotion. Java of course insists that PApplet methods that get overridden have the same public access as in PApplet but adding this is trivial.

Similarly, most code that will compile in NetBeans compiles in the PDE. To avoid problems particularly when there are errors in the code, delete public access for setup(). Processing is perfectly happy with anonymous classes, which may save a few lines of code here and there. Currently generics are not supported. A pity. I like Java generics. They seem to fit nicely with the object model without providing a sledgehammer capability for wholesale obfuscation. Of course, this opinion may be a reaction to an excess of _traits and spiky brackets elsewhere but generics could be useful to reduce code size in some applications. A set of minimalistic controls managed and drawn by Processing for example:


There is little problem implementing a tiny control management subsystem using a state machine: there are less than a dozen states for a complete set and some can be excluded - no drag and drop for example. Similarly, a single Control type with action and graphics closures, implemented as classes and assigned to its attributes on a mix and match basis, keeps coding to a minimum. Controls manage themselves for radio-button selection and which control gets input (focus). Problems come up when programmatic access to a control is needed - to modify text in controls that show drawing status and size for example.

Arguably this is the wrong way to do it. Perhaps closures should monitor status in the wider environment of the enclosing PApplet but doing this means that controls that can otherwise be entirely passive require a specific action closure or active graphic to do the monitoring. A single line of code, in the draw() function say, can modify a passive control. Alternatively a new closure class is required to implement monitoring.

Control action and graphic attributes are coresponding base classes. Access to a specific attribute means either retaining a reference to the control and casting attributes to their actual type or retaining a reference to the attribute itself. Neither way is ideal as far as code size and clarity goes. Generics can do the casting once for a control reference and all specific attribute types are then accessible directly through that reference. There must be other situations where using generics from the PDE could be useful.