// ScrawlBrush // David Bollinger, June 2006 // http://www.davebollinger.com // for Processing 0015 Beta // // wraps up the guts of the original Scrawl applet into a "brush" that can be painted with // Tracker class modified a bit to contain its target and have a limited lifetime // the drawing buffer FixedPointBuffer buf; // the scrawl brush Brush brush; // mouse-down flag boolean isDrawing = false; // flag the applet has mouse focus boolean bGotMouse = false; void setup() { size(480,480,P3D); colorMode(HSB); buf = new FixedPointBuffer(width,height); brush = new Brush(buf, 100, width/2, height/2); } void draw() { // don't move towards mouse until we're sure applet has been focused // (otherwise they all bunch up at 0,0 before first mouse down - yuck) if (bGotMouse) { float targetx = (float)(mouseX); float targety = (float)(mouseY); brush.movetoward(targetx, targety); brush.update(isDrawing); } buf.render(); } void mousePressed() { bGotMouse = true; if (mouseButton == LEFT) { // redundant? yes. but keeps right button from interfering (as with mousePressed) isDrawing = true; } else if (mouseButton == RIGHT) { buf.background(0,0,0); } } void mouseReleased() { if (mouseButton == LEFT) { isDrawing = false; } } void keyPressed() { if (key=='s') saveFrame("scrawl.tga"); } // an 8.16 fixed point rgb buffer with sub-pixel sampling // (the extra precision is used to reduce rounding errors during accumulation) class FixedPointBuffer { static final int shift = 16; // number of .fixed bits static final int fixone = 1<=wid-1.0) || (y>=hei-1.0)) return; // integral coordinates int ix1 = (int)(x); int iy1 = (int)(y); int ix2 = ix1 + 1; int iy2 = iy1 + 1; // fractional coordinates float fractx = x - (float)(ix1); float fracty = y - (float)(iy1); // reciprocal of fractional coordinates float recipx = 1.0 - fractx; float recipy = 1.0 - fracty; // preconvert color values to floats float fr = (float)(r); float fg = (float)(g); float fb = (float)(b); // plot it float ratio; int idx, c; // upper-left ratio = recipx * recipy * alf; idx = iy1 * width + ix1; c = (int)((ratio*fr) + redbuf[idx]); if (c>maxrgb) c=maxrgb; redbuf[idx] = c; c = (int)((ratio*fg) + grnbuf[idx]); if (c>maxrgb) c=maxrgb; grnbuf[idx] = c; c = (int)((ratio*fb) + blubuf[idx]); if (c>maxrgb) c=maxrgb; blubuf[idx] = c; // upper-right ratio = fractx * recipy * alf; idx = iy1 * width + ix2; c = (int)((ratio*fr) + redbuf[idx]); if (c>maxrgb) c=maxrgb; redbuf[idx] = c; c = (int)((ratio*fg) + grnbuf[idx]); if (c>maxrgb) c=maxrgb; grnbuf[idx] = c; c = (int)((ratio*fb) + blubuf[idx]); if (c>maxrgb) c=maxrgb; blubuf[idx] = c; // lower-left ratio = recipx * fracty * alf; idx = iy2 * width + ix1; c = (int)((ratio*fr) + redbuf[idx]); if (c>maxrgb) c=maxrgb; redbuf[idx] = c; c = (int)((ratio*fg) + grnbuf[idx]); if (c>maxrgb) c=maxrgb; grnbuf[idx] = c; c = (int)((ratio*fb) + blubuf[idx]); if (c>maxrgb) c=maxrgb; blubuf[idx] = c; // lower-right ratio = fractx * fracty * alf; idx = iy2 * width + ix2; c = (int)((ratio*fr) + redbuf[idx]); if (c>maxrgb) c=maxrgb; redbuf[idx] = c; c = (int)((ratio*fg) + grnbuf[idx]); if (c>maxrgb) c=maxrgb; grnbuf[idx] = c; c = (int)((ratio*fb) + blubuf[idx]); if (c>maxrgb) c=maxrgb; blubuf[idx] = c; } // render converts 8.shift format back down to 8 bit rgb void render() { for (int idx=area-1; idx>=0; idx--) { // optimized for shift of 16 pixels[idx] = 0xFF000000 | (redbuf[idx] & 0xFF0000) | ((grnbuf[idx] & 0xFF0000) >> 8) | ((blubuf[idx] & 0xFF0000) >> 16); } updatePixels(); } } // a simple "springy follower" thingy class Tracker { float x, y, vx, vy; float targetx, targety; float speed, friction; float lifetime; Tracker(float _x, float _y) { moveto(_x, _y); target(x,y); speed = 0.01; friction = 0.01; lifetime = 1.0; } void moveto(float _x, float _y) { x = _x; y = _y; vx = vy = 0.0; } void target(float tx, float ty) { targetx = tx; targety = ty; lifetime = 5.0 + random(5.0); } void movetoward(float dt) { if (lifetime <= 0.0) return; float axdt = ((targetx-x) * speed) * dt; float aydt = ((targety-y) * speed) * dt; x += vx * dt + 0.5 * axdt * dt; y += vy * dt + 0.5 * aydt * dt; vx += axdt; vy += aydt; float frictiondt = friction * dt; vx -= vx * frictiondt; vy -= vy * frictiondt; lifetime -= dt; if (lifetime < 0.0) lifetime = 0.0; } } class Brush { FixedPointBuffer buf; Tracker loc; Tracker [] tracks; int ntracks; float lastx, lasty; int myred, mygrn, myblu; float myalf, myhue, mysat, mybri, myhueinc; Brush(FixedPointBuffer _buf, int _ntracks, float _x, float _y) { buf = _buf; ntracks = _ntracks; lastx = _x; lasty = _y; loc = new Tracker(_x,_y); loc.speed = 0.1; loc.friction = 0.2; tracks = new Tracker[ntracks]; float basespeed = 0.02; float basefriction = 0.03; for (int i=0; i= 256.0) myhue -= 256.0; color c = color(myhue,mysat,mybri); myred = ((int)red(c)) << buf.shift; mygrn = ((int)green(c)) << buf.shift; myblu = ((int)blue(c)) << buf.shift; } void movetoward(float targetx, float targety) { lastx = loc.x; lasty = loc.y; loc.target(targetx,targety); loc.movetoward(1.0); config(); } void config() { float dx = lastx - loc.x; float dy = lasty - loc.y; if ((dx==0.0) && (dy==0.0)) return; float distsq = dx*dx+dy*dy; // wider and longer with faster movement float len = distsq * 10.0; float wid = sqrt(distsq) * 10.0; // direction travelled float theta = atan2(dy,dx); // orientation of brush is 90-degrees to direction travelled float orient = atan2(-dx,dy); // configure trackers for (int i=0; i