//created=03 Nov 06 //description=Aggregate bristle sketching brush that interacts with Blender. //title=Sketch Board //publish=true //tags=drawing import processing.net.*; import javax.imageio.ImageIO.*; import javax.swing.*; import javax.swing.event.*; Controller controls; JPanel controlPanel; JLabel status = new JLabel(); int lx,ly; int bristles = 20; Vector brush = new Vector(); int splatter = 5; int splotchPercent = 5; float maxSize = 3; float minSize = 1; float overShootMax = .5; float overShootMin = -.5; float dampingMin = 2; float dampingMax = 3; int overShootPercent = 40; String outName = "c://map"; Client myClient; boolean serverMode = false; //shade of grey used to indicate no opacity int alphaRed = 200; Brush standardBristle = new BristleBrush(); Brush marker = new Marker(); Brush pencil = new Pencil(); Brush variWidth = new VariWidth(); Brush bucket = new Bucket(); Brush dropper = new Dropper(); Brush currentBrush = standardBristle; //which brushmodel are we using Vector undoBuffer = new Vector(); void setup(){ //status.setText("Non Blender"); //get the arguments passed in. //applets will nto pass in arguments if (args != null){ for (int i =0; i < args.length; i++){ if (args[i].equals("--Blender")){ status.setText("Connected to Blender"); serverMode = true; } } } controls = new Controller(this); controls.setSize(300,580); controls.show(); smooth(); size(550,800); background(alphaRed); //bump the blank screen into the undoBuffer mouseReleased(); int wi = 0; int wj =0; int bx = 24; /* //this whole part is probably unecessary, but lay down a cool checkerboard int tick = 1; fill(alphaRed,170,170); noStroke(); while(wi < width){ while(wj < height){ rect(wi,wj,bx,bx); wj += 2*bx; } tick++; if (tick == 2) { tick = 0; wj = bx; } else{ wj = 0; } wi += bx; } */ for (int i=0; i < bristles; i++ ){ brush.add(new hair()); } if(serverMode){ //blender networking stuff myClient = new Client(this,"127.0.0.1",50007); } else{ smooth(); } } void keyPressed(){ if (key == 'c'){ background(200); } else if (key =='q'){ if(serverMode)myClient.write("exit"); exit(); } else if (key == 'b'){ export(); } //capture CTRL+Z else if(int(key) == 26){ undo(); } } void undo(){ if (undoBuffer.size()>1){ loadPixels(); PImage un = (PImage)undoBuffer.elementAt(undoBuffer.size()-2); arraycopy(un.pixels,0,pixels,0,pixels.length); updatePixels(); undoBuffer.removeElementAt(undoBuffer.size()-1); } } void export(){ loadPixels(); boolean ex = false; int j = 0; int bt =0,bb=0,bl=0,br=0; //bounds from the top while(j < height && ex == false){ for (int i=0; i < width; i++){ if (red(pixels[j*width + i]) != 200){ ex = true; bt = j -1; break; } } j++; } strokeWeight(1); //bounds from the bottom j = height-1; ex = false; while(j >0 && ex == false){ for (int i=0; i < width; i++){ if (red(pixels[j*width + i]) != 200){ ex = true; bb = j +1; break; } } j--; } //bounds from the left j = 0; ex = false; while(j < width && ex == false){ for (int i=0; i < height; i++){ if (red(pixels[i*width + j]) != 200){ ex = true; bl = j -1; break; } } j++; } //bounds from the right j = width-1; ex = false; while(j > 0 && ex == false){ for (int i=0; i < height; i++){ if (red(pixels[i*width + j]) != 200){ ex = true; br = j +1; break; } } j--; } int iWidth = br-bl; int iHeight = bb-bt; PImage sc = get(); PImage crop = sc.get(bl,bt,iWidth,iHeight); color opaque = color(255); color trans = color(0); PImage alphaMask = new PImage(iWidth,iHeight); for (int i=0; i < iHeight; i++){ for (int jj=0;jj < iWidth; jj++){ if (red(crop.pixels[i*iWidth + jj]) == 200){ alphaMask.set(jj,i,trans); } else{ alphaMask.set(jj,i,opaque); } } } crop.mask(alphaMask); BufferedImage img = new BufferedImage(iWidth,iHeight,BufferedImage.TYPE_INT_ARGB); BufferedImage imgAlpha = new BufferedImage(iWidth,iHeight,BufferedImage.TYPE_INT_ARGB); img.setRGB(0,0,iWidth,iHeight,crop.pixels,0,iWidth); imgAlpha.setRGB(0,0,iWidth,iHeight,alphaMask.pixels,0,iWidth); try{ javax.imageio.ImageIO.write(img,"png",new File(outName+".png")); javax.imageio.ImageIO.write(imgAlpha,"png",new File(outName+"Mask.png")); }catch(Exception e){} if(serverMode){ myClient.write("plane," + iWidth/50 + "," + iHeight/50 + "," + outName+".png" + "," + outName+"Mask.png"); myClient.stop(); } } //this is the undo structure. After they release the mouse copy the current //image into a cicular undo buffer. void mouseReleased(){ loadPixels(); PImage edit = new PImage(width,height); arraycopy(pixels,0,edit.pixels,0,edit.pixels.length); if (undoBuffer.size() > 10){ undoBuffer.removeElementAt(0); } undoBuffer.add(edit); }//of mouseReleased void draw(){ currentBrush.draw(); }//of draw () class hair{ float x,y,lx,ly; float thickness; int hairColor; float damping; public hair(){ thickness = random(minSize,maxSize); hairColor = (int)random(0,32); damping = random(dampingMin,dampingMax); } } public class Controller extends JFrame implements ActionListener{ PApplet owner; String [] brushTypes = {"Bristle", "Marker", "Pencil", "Bucket Fill","Dropper"}; JComboBox brushList = new JComboBox(brushTypes); JButton export = new JButton("Export to Blender"); JButton fileExport = new JButton("Export to File"); JButton fileImport = new JButton("Import from File"); JButton clear = new JButton("Clear"); JCheckBox antialias = new JCheckBox("Antialias"); JCheckBox wonkConnect = new JCheckBox("Connected Noise"); JToggleButton eraseMode = new JToggleButton("Erase"); ColorSwatch brushColor = new ColorSwatch(Color.black); ColorSwatch bucketColor = new ColorSwatch(Color.white); public Controller(PApplet owner){ this.owner = owner; setTitle("Controls"); brushList.addActionListener(this); antialias.addActionListener(this); eraseMode.addActionListener(this); clear.addActionListener(this); export.addActionListener(this); fileExport.addActionListener(this); fileImport.addActionListener(this); buildControls(); } private void buildControls(){ JPanel controlPanel = new JPanel(); controlPanel.setLayout(new BoxLayout(controlPanel,BoxLayout.Y_AXIS)); brushList.setMaximumSize(new Dimension(550,150)); controlPanel.add(brushList); controlPanel.add(currentBrush.getPanel()); controlPanel.add(antialias); controlPanel.add(wonkConnect); controlPanel.add(brushColor); //controlPanel.add(bucketColor); controlPanel.add(eraseMode); //add the control buttons JPanel tm = new JPanel(new FlowLayout()); tm.add(export); if (!serverMode){ export.setEnabled(false); antialias.setSelected(true); } tm.add(fileExport); tm.add(fileImport); tm.add(clear); controlPanel.add(tm); controlPanel.add(status); getContentPane().add(controlPanel); validate(); } public void actionPerformed(ActionEvent e){ if(e.getActionCommand().equals("Clear")){ owner.background(200); } else if (e.getActionCommand().equals("Erase")){ if (eraseMode.isSelected()){ brushColor.setColor(new Color(alphaRed,alphaRed,alphaRed)); } else{ brushColor.setColor(brushColor.lastColor); } } else if(e.getActionCommand().equals("comboBoxChanged")){ getContentPane().removeAll(); if (brushList.getSelectedItem().equals("Bristle")){ currentBrush = standardBristle; } else if (brushList.getSelectedItem().equals("Marker")){ currentBrush = marker; } else if (brushList.getSelectedItem().equals("Pencil")){ currentBrush = pencil; } else if (brushList.getSelectedItem().equals("Bucket Fill")){ currentBrush = bucket; } else if (brushList.getSelectedItem().equals("Dropper")){ currentBrush = dropper; } currentBrush.updateBrush(); buildControls(); } else if(e.getActionCommand().equals("Import from File")){ JFileChooser fc = new JFileChooser("c:\\"); fc.showOpenDialog(owner); File selFile = fc.getSelectedFile(); if (selFile != null){ outName = selFile.getAbsolutePath(); PImage a = loadImage(outName); background(alphaRed); image(a,width/2 - a.width/2,height/2-a.height/2); } } else if(e.getActionCommand().equals("Export to File") || e.getActionCommand().equals("Export to Blender") ){ JFileChooser fc = new JFileChooser("c:\\"); // Show open dialog; this method does not return until the dialog is closed fc.showSaveDialog(owner); File selFile = fc.getSelectedFile(); if (selFile != null){ outName = selFile.getAbsolutePath(); //if (!outName.endsWith(".png") || !outName.endsWith(".PNG")) outName = outName + ".png"; export(); exit(); } } if (antialias.isSelected()){ smooth(); } else{ noSmooth(); } } } class sgSlider extends JPanel{ JSlider slider; JLabel label; public sgSlider(String sText, int minVal, int maxVal, int currValue, ChangeListener listener){ slider = new JSlider(minVal,maxVal,currValue); label = new JLabel(sText); setLayout(new BoxLayout(this,BoxLayout.X_AXIS)); add(label); add(slider); slider.addChangeListener(listener); } public int getVal(){ return slider.getValue(); } } public class ColorSwatch extends JPanel implements MouseListener{ Color setColor; Color lastColor; public ColorSwatch(Color setColor){ this.setColor = setColor; setBackground(setColor); addMouseListener(this); setMaximumSize(new Dimension(400,400)); } public void mouseDragged(MouseEvent e) {} public void mouseMoved(MouseEvent e) {} public void mouseClicked(MouseEvent e) {} public void mousePressed(MouseEvent e) { setColor = JColorChooser.showDialog(frame, "Dialog Title", setColor); if (setColor != null){ setBackground(setColor); currentBrush.updateBrush(); } } public void mouseExited(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} public void setColor(Color newColor){ lastColor = setColor; setColor = newColor; setBackground(newColor); currentBrush.updateBrush(); } } static public void main(String args[]) { //This is significantly more difficult than it should be String [] argv = new String [2]; argv [0] = "sketchBoard"; if (args.length >0){ argv [1] = args[0]; } else{ argv[1] = "--NoBlender"; } PApplet.main(argv); } public interface Brush{ public void draw(); public JPanel getPanel(); public void updateBrush(); } public class BristleBrush implements Brush, ChangeListener{ private JPanel controlPanel; sgSlider oShootMin = new sgSlider("overShootMin",0,50,5,this); sgSlider oShootPercent = new sgSlider("overShootPercent",0,100,10,this); sgSlider numBristles = new sgSlider("numBristles",1,40,20,this); sgSlider minBristle = new sgSlider("BristleMin",0,100,10,this); sgSlider maxBristle = new sgSlider("BristleMax",0,100,30,this); sgSlider dampMax = new sgSlider("damping max",1,10,2,this); sgSlider dampMin = new sgSlider("damping min",1,10,1,this); public BristleBrush(){ controlPanel = new JPanel(); controlPanel.add(oShootMin); controlPanel.add(oShootPercent); controlPanel.add(numBristles); controlPanel.add(minBristle); controlPanel.add(maxBristle); controlPanel.add(dampMin); controlPanel.add(dampMax); } public void draw(){ if (mousePressed){ if (mouseButton == LEFT) { hair current; float dx,dy, overShoot; for (int i=0; i < bristles; i++){ current = (hair)brush.elementAt(i); stroke(current.hairColor); strokeWeight(current.thickness); current.lx = current.x; current.ly = current.y; current.x -= (current.lx - mouseX)/current.damping ; current.y -= (current.ly - mouseY)/current.damping; line(current.x,current.y,current.lx,current.ly); if (random(100) < overShootPercent){ overShoot = random(overShootMin,overShootMax); dx = overShoot * (current.x - current.lx); dy = overShoot * (current.y - current.ly); line(current.x,current.y,current.x + dx,current.y + dy); if(controls.wonkConnect.isSelected()){ current.x += dx; current.y += dy; } } } } } else{ hair current; for (int i=0; i < brush.size(); i++){ current = (hair)brush.elementAt(i); current.x = mouseX; current.y = mouseY; } } } public JPanel getPanel(){ return controlPanel; } public void stateChanged(ChangeEvent e){ bristles = numBristles.getVal(); overShootMax = oShootMin.getVal()/10.f; overShootMin = -overShootMax; overShootPercent = oShootPercent.getVal(); minSize = minBristle.getVal()/10.f; maxSize = maxBristle.getVal()/10.f; dampingMax = dampMax.getVal(); dampingMin = dampMin.getVal(); updateBrush(); } public void updateBrush(){ brush.clear(); hair newHair; while(brush.size() < bristles){ newHair = new hair(); newHair.hairColor = controls.brushColor.setColor.getRGB(); brush.add(newHair); } } } public class Marker implements Brush, ChangeListener{ private JPanel controlPanel; private sgSlider size = new sgSlider("size",0,50,5,this); int myColor, brushSize =20; public JPanel getPanel(){ return controlPanel; } public Marker(){ controlPanel = new JPanel(); controlPanel.add(size); } public void draw(){ if (mousePressed){ if (mouseButton == LEFT) { noStroke(); fill(myColor); ellipse(mouseX,mouseY,brushSize,brushSize); } } } public void updateBrush(){ myColor = controls.brushColor.setColor.getRGB(); } public void stateChanged(ChangeEvent e){ brushSize = size.getVal(); } } public class Pencil implements Brush,ChangeListener{ int lx,ly,x,y; private sgSlider size = new sgSlider("size",0,50,5,this); int brushSize = 2; private JPanel controlPanel; int myColor = 0; public Pencil(){ controlPanel = new JPanel(); controlPanel.add(size); } public JPanel getPanel(){ return controlPanel; } public void draw(){ if (mousePressed){ if (mouseButton == LEFT) { stroke(myColor); lx = x; ly = y; x = mouseX; y = mouseY; strokeWeight(brushSize); line(x,y,lx,ly); } } else{ x = mouseX; y = mouseY; } } public void updateBrush(){ myColor = controls.brushColor.setColor.getRGB(); } public void stateChanged(ChangeEvent e){ brushSize = size.getVal(); } } public class Dropper implements Brush{ private JPanel controlPanel = new JPanel(); public JPanel getPanel(){ return controlPanel; } public void draw(){ if (mousePressed){ if (mouseButton == LEFT) { loadPixels(); controls.brushColor.setColor(new Color(pixels[mouseY*width + mouseX])); } } } public void updateBrush(){ } } public class Bucket implements Brush{ private JPanel controlPanel = new JPanel(); public JPanel getPanel(){ return controlPanel; } public void draw(){ if (mousePressed){ if (mouseButton == LEFT) { loadPixels(); bucketFill(mouseX,mouseY,controls.brushColor.setColor.getRGB(),pixels[mouseY*width + mouseX]); updatePixels(); } } } public void bucketFill(int x, int y, color newColor, color oldColor){ if (oldColor == newColor) return; if (pixels[y*width+x] != oldColor) return; int y1; y1 = y; while(pixels[y1*width+x] == oldColor && y1 < height-1){ pixels[y1*width+x] = newColor; y1++; } y1 = y-1; while(pixels[y1*width+x] == oldColor && y1 > 0){ pixels[y1*width+x] = newColor; y1--; } y1 = y; while(pixels[y1*width+x] == newColor && y1 < height-1){ if(x > 0 && pixels[y1*width+x-1] == oldColor){ bucketFill(x-1,y1,newColor,oldColor); } y1++; } y1 = y-1; while(pixels[y1*width+x] == newColor && y1 >= 0){ if(x > 0 && pixels[y1*width+x-1] == oldColor){ bucketFill(x-1,y1,newColor,oldColor); } y1--; } // y1 = y; while(pixels[y1*width+x] == newColor && y1 < height){ if(x < width-1 && pixels[y1*width+x+1] == oldColor){ bucketFill(x+1,y1,newColor,oldColor); } y1++; } y1 = y-1; while(pixels[y1*width+x] == newColor && y1 >= 0){ if(x < width-1 && pixels[y1*width+x+1] == oldColor){ bucketFill(x+1,y1,newColor,oldColor); } y1--; } } public void updateBrush(){ } } public class VariWidth implements Brush{ int lx,ly,x,y; float ax,ay,bx,by,cx,cy,ex,ey; private JPanel controls; public JPanel getPanel(){ return controls; } public void draw(){ if (mousePressed){ if (mouseButton == LEFT) { noStroke(); fill(0); lx = x; ly = y; x = mouseX; y = mouseY; float dx = (x - lx); float dy = (y - ly); if (dx >= 0) dx = 10; else dx = 10; if (dy >0) dy = 10; else dy = -10; ax = x-dy; ay = y+dx; bx = x+dy; by = y-dx; quad(ax,ay,bx,by,cx,cy,ex,ey); //quad(mouseX,mouseY,ax,ay,mouseX,mouseY,bx,by); //copy current settings to last settings cx = bx; cy = by; ex = ax; ey = ay; } } else{ x = mouseX; y = mouseY; ax = mouseX; ay = mouseY; bx = mouseX; by = mouseY; cx = mouseX; cy = mouseY; ex = mouseX; ey = mouseY; } } public void updateBrush(){ } }