Drawing into a BufferedImage


    Jun 10 2006 | 4:00 pm
    There weren't many mxj examples of how to draw into a BufferedImage, so I decided to supply one. Hope it's useful--I'm sure what MaxMSP most needs are business charts, so it can compete with Excel.
    -- Paul
    import com.cycling74.max.*; import com.cycling74.jitter.*; import java.awt.*; import javax.swing.*; import java.awt.geom.*; import java.awt.image.BufferedImage;
    // created by Ignotus, June 9, 2006.
    /** * Creates a pie graph in a BufferedImage and then pops it into a * JittterMatrix for output. * List of numbers in left inlet sets pieValues attribute, draws graph * and triggers output at left outlet. * List of numbers in right inlet sets pieValues attribute. * Bang in left inlet triggers output if pieValues attribute * contains numeric values. * Bang in right inlet outputs pieValues at info outlet. */ public class buf2jmTest extends MaxObject {
    private static final String[] INLET_ASSIST = new String[]{ "Bang or list of numbers", "List of numbers" }; private static final String[] OUTLET_ASSIST = new String[]{ "Jitter Matrix" };
    String useMessage = "Enter a list of positive numbers in inlet 1 or 2, or set the pieValues attribute."; public float[] pieValues = new float[0];
    public buf2jmTest(Atom[] args) { declareInlets(new int[]{DataTypes.ALL, DataTypes.ALL}); declareOutlets(new int[]{DataTypes.ALL}); setInletAssist(INLET_ASSIST); setOutletAssist(OUTLET_ASSIST); declareAttribute("pieValues", "pieValues", "setPieValues"); }
    public void bang() { int inletNum = getInlet(); if ( 0 == inletNum ) { if (0 == pieValues.length) { post("You must supply a list of positive numbers first, or set the pieValues attribute."); } else { outputPieChart(); } } else { Atom[] outputList = Atom.newAtom(pieValues()); int infoOutletNum = getInfoIdx(); outlet( infoOutletNum, "list", outputList ); } }
    public void list(Atom[] list) { int inletNum = getInlet(); boolean goodInput = true; // check for the right sort of list if ( list.length < 1 ) { post(useMessage); return; } Atom atom; float[] valueList = new float[list.length]; for ( int i = 0; i < list.length; i++ ) { atom = list[i]; if ( !atom.isInt() && !atom.isFloat() ) { goodInput = false; break; } valueList[i] = atom.getFloat(); if ( valueList[i] < 0 ) { goodInput = false; break; } } if ( goodInput ) { setPieValues(valueList); if ( 0 == inletNum ) { outputPieChart(); } } else { post(useMessage); } }
    private float[] pieValues() { return pieValues; } private void setPieValues(float[] newPieValues) { pieValues = newPieValues; }
    public static BufferedImage makeBufferedImage(Image image) { BufferedImage bufferedImage = new BufferedImage( image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB); Graphics2D g2 = bufferedImage.createGraphics(); g2.drawImage(image, null, null); return bufferedImage; }
    public static float sum( float[] values ) { float sum = 0.0f; for ( int i = 0; i < values.length; i++ ) { sum += values[i]; } return sum; }
    public BufferedImage bakePieGraph() { int imageWidth = 212; int imageHeight = 200; Color bgColor = new Color(255, 248, 228); // fff8e4 Color colors [] = {Color.blue, Color.green, Color.red, Color.cyan, Color.magenta, Color.gray, Color.yellow, Color.pink, Color.orange, Color.white, new Color(89, 55, 233)}; Color penColor = Color.black; Frame frame = new Frame (); // addNotify () is required to create an image (this will call Toolkit.getDefaultToolkit ()) // addNotify "creates the frame's peer, its actual resource in whatever native window toolkit you're working on" // cf. p. 188 Java 2D Graphics, Jonathon Knudsen, O'Reilly Pub. frame.addNotify (); Image pieImage = frame.createImage(imageWidth, imageHeight); // use a Graphics2D to get better imaging Graphics2D gc = (Graphics2D)pieImage.getGraphics (); gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // fill and outline the background Shape bgRect = new Rectangle2D.Double(0, 0, imageWidth, imageHeight); gc.setPaint(bgColor); gc.fill(bgRect); gc.setPaint(new Color(128, 128, 128)); gc.setStroke(new BasicStroke(2)); // 2 pixel wide stroke gc.draw(bgRect); // Draw whole pie in background color with a gray drop shadow Stroke stroke_1px = new BasicStroke(1); // 1 pixel wide stroke gc.setStroke(stroke_1px); int inset = 16; Shape pie = new Ellipse2D.Double(10, 8, imageHeight - inset, imageHeight - inset); Shape pieShadow = new Ellipse2D.Double(16, 11, imageHeight - inset, imageHeight - inset); gc.setPaint(new Color(192, 192, 192)); gc.fill(pieShadow); gc.draw(pieShadow); gc.setPaint(bgColor); gc.fill(pie); gc.setPaint(penColor); gc.draw(pie); // Compute the sum of all values float sum = sum( pieValues() ); // define an arc shape for slices Rectangle2D pieBounds = pie.getBounds2D(); Arc2D pieArc = new Arc2D.Double(pieBounds, 0, 15, Arc2D.PIE); // step pie values and draw pie slices // note that graphics state is set to 1 pixel stroke for (int accum = 0, i = 0; i < pieValues.length; i++) { float answerValue = pieValues[i]; float startAngle = accum * 360.0f / sum; float angle = answerValue * 360.0f / sum; // Fill the pie slice for current range pieArc.setArc(pieBounds, startAngle, angle, Arc2D.PIE); gc.setPaint (colors [i % colors.length]); gc.fill(pieArc); gc.setPaint(penColor); gc.draw(pieArc); accum += answerValue; } return makeBufferedImage(pieImage); }
    public void outputPieChart() { // the drawing code is probably very low impact, but it's still a // good idea to wrap it in a Thread to free up processor time. (new Thread() { public void run() { BufferedImage bim = bakePieGraph(); JitterMatrix jim = new JitterMatrix(bim); outlet(0, "jit_matrix", jim.getName()); } }).start(); }
    }
    -- ----- |(*,+,#,=)(#,=,*,+)(=,#,+,*)(+,*,=,#)| -----

    • Feb 03 2007 | 2:31 am
      Wow! First time i've copied and pasted anything more complicated than Hello World and it's worked first time! :-) (after a bit of live wrapping)
      Thanks! You're right - there aren't many examples of this!
      jonny
    • Jul 07 2010 | 5:57 pm
      Thanks for posting the code for making pie charts. I know it was many years ago and things might have changed. I tried to use it on the latest version of MAX/MSP/Jitter 5.14 running on a mac and got the following errors after I copied and pasted the code in a new patcher:
      Could not load class 'buf2jmTest' Could not load class 'buf2jmTest' import: no such object import: no such object import: no such object import: no such object import: no such object import: no such object //: no such object June: no such object ...etc.
      Would you have any suggestions on how I should proceed to make this work?
    • Jul 07 2010 | 8:05 pm
      You will need to compile the class. Are you using OSX or windows?
      in the case you are on 10.6.4 here's a compiled version.
      Hope it helps!
    • Jul 07 2010 | 9:34 pm
      Thank you, I am using OSX 10.6.4 and it worked fine. Much appreciated.
      Val