Happy Holidays! It’s been ages since I posted anything, mostly because I’ve been so busy teaching at UCA Farnham and Openlab Workshops.
Here’s a little something for the holidays. The code is below, and the source image was courtesy of Jan Tik on Flickr.
/** * Use some pretty particles to trace an image * Thanks to Flight404 and Cinder for much inspiration: * http://libcinder.org/docs/v0.8.2/hello_cinder.html * Also, I used Jan Tik's CC-licensed image from flickr: * http://www.flickr.com/photos/jantik/308709862/ * * Copyright (C) 2010 Evan Raskob <evan@flkr.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * This program 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 Affero General Public License for more details. * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ import processing.opengl.*; import javax.media.opengl.*; // uses toxi's toxiclibs for vector functions.. because it's easier - // http://hg.postspectacular.com/toxiclibs/wiki/Home import toxi.geom.Vec2D; final String imgName = "Winter Meal - jantik.jpg"; // make sure this is in your data folder! PImage bgImage; ArrayList<Particle> particles = new ArrayList<Particle>(); float D; // base diameter of all particles float mass = 0.2; // universal mass of all particles float DIST = 80*80; // min distance for forces to act on float MIN_DIST = 10; // min dist between mouse positions for adding new particles boolean drawLines = false; // draw lines btw particles? boolean saveFrames = false; // save frames sequentially to disk? int frameIndex = 0; // index of saved frame - better than the built-in version void setup() { // size should be the size of your image. you could make it dynamic if you want... size(640,480,OPENGL); hint(ENABLE_OPENGL_4X_SMOOTH) ; strokeWeight(2.0); D = min(width,height) / 30; // base diameter of particles on screen size. change this for bigger/smaller particles bgImage = loadImage(); // background image DIST = D*D*1.5*1.5; // distance between particles. Smaller = more detail in final image MIN_DIST = D/4; // see above } void draw() { PGraphicsOpenGL pgl; GL gl; // *** blending setup *** // pgl = (PGraphicsOpenGL) g; gl = pgl.beginGL(); gl.glDisable(GL.GL_DEPTH_TEST); gl.glEnable(GL.GL_BLEND); // gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE ); gl.glBlendFunc( GL.GL_SRC_ALPHA , GL.GL_ONE_MINUS_DST_COLOR ); pgl.endGL(); // *** end blending setup *** // // load pixels, for getting source colors bgImage.loadPixels(); smooth(); background(0); image(bgImage,0,0); // draw image first. // add a new particle if the mouse is pressed if (mousePressed && abs(pmouseX-mouseX) > MIN_DIST) { Particle p = new Particle((float)mouseX, (float)mouseY, D); // p.v.x = 0.01*(pmouseX-mouseX); // p.v.y = 0.01*(pmouseY-mouseY); // set max velocity based on screen size p.MAXV.x = width/200.0; p.MAXV.y = height/200.0; particles.add(p); } // optional - keep track of "dead" particles, to remove later ArrayList<Particle> deadParticles = new ArrayList<Particle>(); noStroke(); // go through the particles and update their position data for (Particle p : particles) { p.update(); if (p.alive) { color c = bgImage.pixels[((int)p.pos.y)*bgImage.width + (int)p.pos.x]; p.draw(c); } else deadParticles.add(p); } // not using this yet... but could... for (Particle p : deadParticles) { particles.remove(p); p = null; } // handle inter-particle forces repulseParticles(); // save frames in a sequence if (saveFrames) { saveFrame("frame-" + nf(frameIndex,6)+".png"); ++frameIndex; } } void keyReleased() { if (key == ' ') { saveFrame("winter" + random(0,999999) + ".png"); } else if (key == 'l' || key == 'L') drawLines = !drawLines; else if (key == 'f') saveFrames = !saveFrames; } void repulseParticles() { for(int i=0; i<particles.size(); ++i) { Particle p0 = particles.get(i); for(int ii=i+1; ii<particles.size(); ++ii ) { Particle p1 = particles.get(ii); Vec2D dir = p0.pos.sub(p1.pos); float distSquared = dir.magSquared(); if( distSquared > 0.0f && distSquared <= DIST) { dir.normalize(); float F = min(0.2, 1.0f/distSquared) / mass; dir.scaleSelf( F ); if (drawLines) { //stroke(255,80); stroke(0,200,0); stroke(p0.c); line(p0.pos.x, p0.pos.y, p1.pos.x, p1.pos.y); } p0.a.addSelf(dir); p1.a.subSelf(dir); } } } } // a simple Particle class with acceleration and velocity class Particle { Vec2D MAXV = new Vec2D(2,2); // max velocity this particle can have (absolute) Vec2D pos; // position Vec2D v; // instantaneous velocity Vec2D a; // instantaneous acceleration float d; // diameter color c; // color boolean alive = false; Particle(float _x, float _y, float _d) { pos = new Vec2D(_x,_y); v = new Vec2D(); a = new Vec2D(); d = _d; alive = true; } void draw(color _c) { c = _c; fill(c); float bd = d*(0.9*brightness(c)/255.0 + 0.1); ellipse(pos.x, pos.y, bd,bd); } void update() { if (v.x > MAXV.x) v.x = MAXV.x; if (v.x < -MAXV.x) v.x = -MAXV.x; if (v.y > MAXV.y) v.y = MAXV.y; if (v.y < -MAXV.y) v.y = -MAXV.y; pos.addSelf(v); v.scaleSelf(0.95); v.x += a.x; v.y += a.y; a.scaleSelf(0.5); // if (pos.x >= width || pos.x <= 0 || // pos.y >= height || pos.y <= 0) if (pos.x >= width || pos.x <= 0) { //alive = false; pos.x = constrain(pos.x, 0, width-1); v.x = -v.x; a.x = 0; } if (pos.y >= height || pos.y <= 0) { //alive = false; pos.y = constrain(pos.y, 0, height-1); v.y = -v.y; a.y = 0; } } }