// Pierre MARZIN // Trying to learn Processing... First project, much inspired by "Mycelium" by Scloopy? // http://marzinp.free.fr/applets //GUI and Drag 'n drop: great libraries ! Thanks to Andreas Schlegel (http://www.sojamo.de/libraries/index.html) import controlP5.*; import sojamo.drop.*; //GUI variables SDrop drop; public String mUrl; ControlP5 controlP5; Textlabel weightlabel; Textlabel seuilmaxlabel; Textlabel seuilminlabel; Textlabel modeCouleurLabel; Textlabel pInfo; Textlabel rangelabel; RadioButton radio ; Button onOffButton; Button soundButton; Button resetButton; Button limitButton; //PGraphics pInfo; String monInfo; PFont f; //I just wanted to play with sound a bit. /*sound import ddf.minim.*; import ddf.minim.signals.*; Minim minim; AudioOutput out; SineWave [] sine; boolean useSound;*/ PImage source; // Source image //for each "worm", variables: position //position PVector [] p; //speed PVector [] v; //aimed direction PVector vise=new PVector(); //"seeking" vector PVector deviation=new PVector(); //worm's length int [] longueurVer; //worms maxwidth int []maxwidth; //worms decreaserate int [] decreaserate; //like pixels[], to keep track of how many times a point is reached by a worm int [] buffer; boolean[] typeVer; int [] limiteDown; float [] seekerWeight; //number of worms at the beginning int nbePoints; //width and length of the drawing area int larg; int haut; int largI; int hautI; //brightness of the destination pixel float briMax; //minimum brightness threshold public int seuilBrillanceMini; //maximum brightness threshold public int seuilBrillanceMaxi; //location of the tested point in pixels[] int locTest; //around a point (worms position), how many pixels will we look at... int amplitudeTest; //constrain the aimed direction vector into an r radius circle int r; //constrain the acceleration vector into a devMax radius circle float devMax; //constrain the speed vector into a vMax radius circle int vMax; //not used:random factor int hasard; //stroke's weight (slider) or radius of ellipse used for drawing public float myweight; //draw or not (button onOffButton) public boolean dessine; //different drawing options public int modeCouleur; //fill color int macouleur; boolean limite; //setup only sets up what won't change:GUI and window params //I use initialiser() to set up what has to be initialised //when you hit "ResetButton" and dessin() to set the drawing parameters void setup() { larg=largI=900; haut=hautI=600; size(1000,750); //sound useSound=false; limite=false; //sound minim = new Minim(this); drop = new SDrop(this); f = loadFont("ArialUnicodeMS-12.vlw"); source = loadImage("tiger.jpg"); if(hautI*source.width>largI*source.height){ larg=largI; haut=larg*source.height/source.width; }else{ haut=hautI; larg=haut*source.width/source.height; } source.resize(larg,haut); source.loadPixels(); //info text textFont(f); fill(0); //GUI controlP5 = new ControlP5(this); controlP5.setControlFont(new ControlFont(createFont("ArialUnicodeMS",10,true),12)); // add radioButton for drawing modes radio = controlP5.addRadioButton("radioButton",110,haut+115); //radio.disableCollapse(); radio.setColorForeground(color(120)); radio.setColorLabel(color(0)); radio.setItemsPerRow(4); radio.setSpacingColumn(100); radio.addItem("Normal",1); radio.addItem("Pic Colors",2); radio.addItem("Growing",3); radio.activate("Normal"); //add switch button for limit to threshold limitButton= controlP5.addButton("short_strokes",0,250,haut+40,110,15); limitButton.setSwitch(true); //add onOffButton,sound and ResetButton Buttons onOffButton=controlP5.addButton("onOff",0,610,haut+40,50,15); onOffButton.setSwitch(true); //sound soundButton=controlP5.addButton("sound",0,680,610,50,15); //sound soundButton.setSwitch(true); resetButton=controlP5.addButton("Reset",0,750,haut+40,50,15); // add horizontal sliders and labels for stroke, threshold and drawing modes controlP5.addSlider("myweight",1,15,1,100,haut+40,100,15); Slider s2 = (Slider)controlP5.controller("myweight"); s2.setLabel(""); s2.setDecimalPrecision(0); s2.setNumberOfTickMarks(10); s2.setSliderMode(Slider.FIX); weightlabel = new Textlabel(controlP5,"Stroke width:",10,haut+40,10,10); weightlabel.setColorValue(0); Range range=controlP5.addRange("rangeController",0,255, 0,128, 100,haut+85,200,15); seuilBrillanceMaxi=128; seuilBrillanceMini=0; range.setDecimalPrecision(0); range.setCaptionLabel(""); range.setSliderMode(Slider.FIX); seuilmaxlabel = new Textlabel( controlP5,"light",350,haut+80,10,10); seuilmaxlabel.setColorValue(0); seuilminlabel = new Textlabel(controlP5,"dark",10,haut+80,10,10); seuilminlabel.setColorValue(0); modeCouleurLabel = new Textlabel(controlP5,"Drawing modes:",10,haut+110,10,10); modeCouleurLabel.setColorValue(0); pInfo=new Textlabel( controlP5,"You can drag and drop a picture (not too big!) from your web or file browser...",10,haut+10,10,10); pInfo.setColorValue(0); rangelabel=new Textlabel(controlP5,"Range",180,haut+67,10,10); rangelabel.setColorValue(0); //frameRate(15); initialiser(); } //launched after setup and any time you hit the ResetButton button void initialiser() { nbePoints=6; weightlabel.draw(this); seuilmaxlabel.draw(this); seuilminlabel.draw(this); modeCouleurLabel.draw(this); rangelabel.draw(this); pInfo.draw(this); //sound minim.stop(); // get a line out from Minim, default bufferSize is 1024, default sample rate is 44100, bit depth is 16 //sound out = minim.getLineOut(Minim.STEREO); //sound sine=new SineWave[nbePoints]; fill( 255 ); stroke( 255 ); rect( 0, 0, larg,haut ); onOffButton.setOn(); buffer=new int[haut*larg]; smooth(); dessine=true; p=new PVector[nbePoints]; v=new PVector[nbePoints]; longueurVer=new int[nbePoints] ; seekerWeight=new float[nbePoints]; limiteDown=new int[nbePoints] ; typeVer=new boolean[nbePoints]; decreaserate=new int[nbePoints]; maxwidth=new int[nbePoints]; for(int i=0;i<nbePoints;i++) { v[i]=new PVector(3-random(6),3-random(6)); longueurVer[i]=0; limiteDown[i]=0; decreaserate[i]=10+int(random(100)); maxwidth[i]=1+int(random(20)); seekerWeight[i]=random(myweight); if(random(4)<2){ typeVer[i]=true; }else{ typeVer[i]=false; } // create a sine wave Oscillator, set to 440 Hz, at 0.5 amplitude, sample rate from line out //sound sine[i] = new SineWave(200+200*i, 0.02, out.sampleRate()); // set the portamento speed on the oscillator to 200 milliseconds //sound sine[i].portamento(200); // add the oscillator to the line out //sound out.addSignal(sine[i]); p[i]=new PVector(random(larg),random(haut)); while((brightness(source.pixels[floor(p[i].x)+floor(p[i].y)*larg])>seuilBrillanceMaxi)||(brightness(source.pixels[floor(p[i].x)+floor(p[i].y)*larg])<seuilBrillanceMini)) { p[i].x=int(random(larg)); p[i].y=int(random(haut)); } } //sound out.noSound(); } void draw() { if(mUrl!=null) { source=null; source=loadImage(mUrl); if(hautI*source.width>largI*source.height){ larg=largI; haut=larg*source.height/source.width; }else{ haut=hautI; larg=haut*source.width/source.height; } // println(source.width+" "+source.height); background(200); source.resize(larg,haut); source.loadPixels(); initialiser(); mUrl=null; } if(dessine) { //sound if (useSound) {out.sound();} dessin(); } /*sound else { if (useSound) {out.noSound();} }*/ } void dessin() { amplitudeTest=2; vise.x=0; vise.y=0; r=12; hasard=0; devMax=2; vMax=50; locTest=0; //pour chaque mobile for(int t=0;t<p.length;t++) {//X.length //on va tester les pixels autour du mobile p[t] en direction de la vitesse du mobile //calcul du barycentre des points testés pondérés de la brillance (vise.x, vise.y) vise.x=0; vise.y=0; deviation.x=0; deviation.y=0; for (int i=-amplitudeTest+floor(deviation.x);i<amplitudeTest+1+floor(deviation.x);i++) {///rdessin for (int j=-amplitudeTest+floor(deviation.y);j<amplitudeTest+floor(deviation.y)+1;j++) { locTest=floor(p[t].x)+i+(floor(p[t].y)+j)*larg; //on vérifie que le point fait partie de l'image if ((p[t].x+i>0)&& (p[t].y+j>0)&&(p[t].x+i<larg-1)&&(p[t].y+j<haut-1)) { vise.x+=i*(255-brightness(source.pixels[locTest])); vise.y+=j*(255-brightness(source.pixels[locTest])); } } } //on contraint vise.x et vise.y dans un cercle de rayon r //qd r augmente, les mobiles tournent plus serrés if ((p[t].x>0)&& (p[t].y>0)&&(p[t].x<larg-1)&&(p[t].y<haut-1)) { //buffer est une copie vide de l'image. on l'augmente pour chaque point teste buffer[floor(p[t].x)+floor(p[t].y)*larg]++; } //si on est passé plus de 10 fois on demenage le point if(buffer[floor(p[t].x)+floor(p[t].y)*larg]>10) { //source.pixels[floor(p[t].x)+floor(p[t].y)*larg]=0; deplacePoint(t); } //on compte les segments de chaque ver longueurVer[t]++; /*//si c'est trop long on déménage if (longueurVer[t]>200){ longueurVer[t]=0; deplacePoint(t);}*/ //coeur du comportement de seeker: vise.normalize(); vise.mult(r); v[t].add(new PVector(vise.x,vise.y)); //+hasard-random(2*hasard) deviation=v[t].get(); deviation.normalize(); deviation.mult(devMax); v[t].normalize(); v[t].mult(vMax); p[t].x=p[t].x+deviation.x; p[t].y=p[t].y+deviation.y; //if (t==1){print(" x:"+p[t].x+" p[t].x1:"+(p[t].x-deviation.x/rdessin)+" y:"+p[t].y+" p[t].y2:"+(p[t].y-deviation.y/rdessin)+" ");} //noLoop(); if (((p[t].x>0)&& (p[t].y>0)&&(p[t].x<larg-1)&&(p[t].y<haut-1))&&((limite)&&((brightness(source.pixels[floor(p[t].x)+floor(p[t].y)*larg])<=seuilBrillanceMaxi)&&(brightness(source.pixels[floor(p[t].x)+floor(p[t].y)*larg])>=seuilBrillanceMini)))) {if (limiteDown[t]!=0){limiteDown[t]-=2;} } if (((p[t].x<0)|| (p[t].y<0)||(p[t].x>larg-1)||(p[t].y>haut-1))||((limite)&&((brightness(source.pixels[floor(p[t].x)+floor(p[t].y)*larg])>seuilBrillanceMaxi)||(brightness(source.pixels[floor(p[t].x)+floor(p[t].y)*larg])<seuilBrillanceMini)))) {if (limiteDown[t]==0){limiteDown[t]=2;} limiteDown[t]+=2;//print(limiteDown[t]+" "); if (limiteDown[t]>=52/myweight){ limiteDown[t]=0;deplacePoint(t);} } if ((p[t].x<0)|| (p[t].y<0)||(p[t].x>larg-1)||(p[t].y>haut-1)||((deviation.x==0)&&(deviation.y==0)))//|| {limiteDown[t]=0; deplacePoint(t); } else { briMax=brightness(source.pixels[floor(p[t].x)+floor(p[t].y)*larg]); } //if (brightness(source.pixels[floor(p[t].x)+floor(p[t].y)*larg])<seuilBrillanceMaxi) //strokeWeight(myweight); //strokeJoin(ROUND); //strokeCap(ROUND); if(modeCouleur==1) { macouleur=0; }//stroke(macouleur,15); if(modeCouleur==2) { macouleur=source.pixels[floor(p[t].x)+floor(p[t].y)*larg]; }//stroke(macouleur,15); if(modeCouleur==3) { float vert=200;//50+(100*(1-sin(longueurVer[t]/200))); float rouge=50*(1-cos(longueurVer[t]/300)); float bleu=100*(1-sin(longueurVer[t]/100));//100-vert-rouge; macouleur=color(rouge,vert,bleu); }//stroke(macouleur,15); //int macouleur=source.pixels[floor(p[t].x)+floor(p[t].y)*larg]; //stroke(color(0,green(macouleur),blue(macouleur)),15);//briMax //line(round(p[t].x-deviation.x),round(p[t].y-deviation.y),round(p[t].x),round(p[t].y)); noStroke(); float incr=seekerWeight[t]*(1-sin(float(longueurVer[t])/decreaserate[t])); fill(macouleur,-limiteDown[t]+300/incr); //print( limiteDown[1]+" "); ellipse(p[t].x,p[t].y,incr,incr); //myweight+=0.001; //if(myweight>10)myweight=1; /*sound if (useSound) { sine[t].setFreq(900-p[t].y); sine[t].setPan((p[t].x/512)-1); }*/ //important: on cree un nouveau vers de temps en temps (on pourrait tester selon la brilance de la zone...) if (random(1)>1-(255-briMax)/80000) {//0.997 p=(PVector[])append(p,new PVector(p[t].x,p[t].y)); v=(PVector[])append(v,new PVector(v[t].x*(5+random(6)),v[t].x*(5+random(6)))); /*sound sine=(SineWave[])append(sine,new SineWave(p[t].y, 0.02, out.sampleRate())); if (random(1)>0.998) { out.addSignal(sine[sine.length-1]); }*/ longueurVer=append(longueurVer,0); seekerWeight=append(seekerWeight,random(myweight)); limiteDown=append(limiteDown,0); decreaserate=append(decreaserate,10+int(random(100))); } } } void deplacePoint(int t) { p[t].x=int(random(larg)); p[t].y=int(random(haut)); while((brightness(source.pixels[floor(p[t].x)+floor(p[t].y)*larg])>seuilBrillanceMaxi)||(brightness(source.pixels[floor(p[t].x)+floor(p[t].y)*larg])<seuilBrillanceMini)) { p[t].x=int(random(larg)); p[t].y=int(random(haut)); } } void dropEvent(DropEvent theDropEvent) { // si le drop est une image jpg // on charge l'adresse dans mUrl. if(theDropEvent.isFile()) { mUrl = theDropEvent.filePath(); } if(theDropEvent.isURL()==true) { if (theDropEvent.url().trim().substring(theDropEvent.url().trim().length()-3,theDropEvent.url().trim().length())=="jpg") { mUrl = theDropEvent.url().trim(); } } } public void rangeController(ControlEvent theControlEvent) { if(theControlEvent.isFrom("rangeController")) { // min and max values are stored in an array. // access this array with controller().arrayValue(). // min is at index 0, max is at index 1. seuilBrillanceMini = int(theControlEvent.getController().getArrayValue(0)); seuilBrillanceMaxi = int(theControlEvent.getController().getArrayValue(1)); } } public void onOff() { if(dessine) { dessine=false; //sound if(useSound){soundButton.setOff();useSound=false;} } else { dessine=true; } } public void short_strokes() { limite=!limite; } /*sound public void sound() { if(useSound) { out.noSound(); useSound=false; } else { out.sound(); useSound=true; } }*/ public void Reset() { /*sound if (useSound) { out.close(); out.clearSignals(); minim.stop(); super.stop(); }*/ initialiser(); } void controlEvent(ControlEvent theEvent) { if(theEvent.isGroup()) { modeCouleur = int(theEvent.group().value()); } }