block by dribnet c2d4a99516752eefa120b6b3689843f1

Substrate by Jared Tarbell (2003)

Full Screen

Port of Jared Tarbell’s Substrate from processing to p5.js. Original source is also archived in this repository and care was taken to change as little as possible from the original.

Mouse press restarts the program.

port by @dribnet July 2021

index.html

<head>
    <script language="javascript" type="text/javascript" src="p5_1.3.1.js"></script>
    <script language="javascript" type="text/javascript" src="z_purview_helper.js"></script>    
    <script language="javascript" type="text/javascript" src="substrate.js"></script>
    <style>
        body   { padding: 0; margin: 0; }
    </style>
</head>
<body style="background-color:WhiteSmoke;">
    <div class="outer">
        <div class="inner">
            <div id="canvasContainer"></div>
        </div>
    </div>
<br>
</body>

pollockShimmering.gif

GIF89a�
�0 8( (88(PX(`88@0XX8XPHHHXhXXXhXPX`Xhhhhp`pphpx�0�(�8�@�X�X�X�x�PH�hH�xH�`p�hp�px�� ��0��(�(��(��(��(��H��h��h��h��x��p��x��x��p��x�P��`Ȱ`Ƞx��xȰxаx�`�x��X��H��X��P��P��p��h��x��x��p��p��h��p��x��x��xxx�p�����������������������������������а����и�ఀ���ȸ�а��������������Ș�Ȁ����Ș�Ș�И�ؐ�И�ؘ����������������������Ƞ�Ƞ����Ȩ����ȸ����Ȱ�Ȱ�а�Ƞ�Ȩ�Р�ؠ�ب�ؠ�ب�а�ذ�ظ�ذ�и�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������,�
�}ʼn,ؙ3�d�(Q�PŊ����OAW�0#v�"D�ժ%L�%=zB�֛7�=J��M�#Gܸ�LJK�:uʔI��W�0C�b�1bL��%J�R
x�T��)R�	�&MĈ�T��+V�.�(!CF�S4�A�',\���c�3fԨ!BD5j4iJ����!-j�Ȍ���
!b�ӧ'O0aj��˗V�����<y�xA�L�2�٣H�
t�2l؅�&m��.;v��K��M�����B�	
8pH��3�
"T��!D�-�G�"�dJ*��L2�dN:��PBeRJ1�TRQeVZq�W`�E�Yg��V[o�5W]w�W_6Xa���b�=�d�]fJf�u�Y��V�i����k��V�m����o�
W�q�-��s�MWLu�e�]w߅7^y祷^{�
T�A	-��CMT�Em��G!�T�I)���K1�T�M9���OA
U�QI��TSOE51U]��V]}�Xe�U��l��\tم�^|��`����b�9�d�Y��!�q�h��fj���l��fn���p�gr�1�t�C�u�iǝw��G�y�Ǟ{�9���ՙ���	��j ���`�2�Jja�b����鈢���+��b�1�J����x+����k����*yl��B���Z9m��r���azKf�g���m�'}s�g�~���_�Z�0� �
.
��F:酖j�i���*�h�Uj���8��0�:��7֪#�=�ګ��9l��2����J�l��bY��z	f�c�k�i���	h���H �E��C��2���P,�*��K ��Ql8��K���C,���J+iE4��d��(���,�TRI$����A[lq�
N8!�$W
0@h�\P�
*��B��Q��e�@"�:�@B�E���8�mC�ƣ^��#����T$a!�J�(B�\��:�+C����D�J����l���BЄ&XAL}��&$ ��\�@���I�r�Ü�8�9Љ��3�T�:��Nv���t�;�O�#^0�w	�)�y΃����aO{���J!>�}�c��,?_ȯ�����?�0!!�)�صD��hI$"�B�����]���EZ������4�A�pe(C\���c#x@t���!�?0�Z��ΰ�V��Ї>���Z��"�8��].s����B7���t�[]�^����v��]�~�b�x�8�^�׼�Eozջ�����}/|�+��ҷ�������w�T�o��_e�HYt$- )IJ�’��&5�IO�R�� �.L�JU����Eey	Z����/��K`
�����ihCe2әДf�ɇkfs�E�\7�N&��Q<��i�vf�\��ÈO}���jhJ�8��	�#C��ЈNT�%dF��8(��"%)-&Y��t����'C9�R�2��� N_�Y��ܥ/z��`ӅI5&S���f>3�Ӥ�U|���#zS��l"9�hNtRq�Wt���E���c�g��F�1�o,�jDž�ѡ|��%
H����hG?R6<2�\�IQ��LF֥���ek��V�t���ih�:Z�����U�1��Z��v��L6i;

substrate.js

// Substrate Watercolor
// j.tarbell   June, 2004
// Albuquerque, New Mexico
// complexification.net

// p5.js port by @dribnet July 2021

const canvasWidth = 960;
const canvasHeight = 500;

let dimx = canvasWidth;
let dimy = canvasHeight;
let num = 0;
// maxnum value interpolated between "medium" and "large"
let maxnum = 175;

// grid of cracks
let cgrid = [];
let cracks = [];

// sand painters
let sands = [];

function setup() {
  let can = createCanvas(canvasWidth, canvasHeight);
  can.parent('canvasContainer');  

  cgrid = Array(dimx*dimy).fill(0);
  cracks = [];

  begin();
}

function draw() {
  // crack all cracks
  for (let n=0; n<num; n++) {
    cracks[n].move();
  }
}

function mousePressed() {
  begin();
}

// added by dribnet for screen caps
function keyTyped() {
  if (key == '!') {
    saveBlocksImages();
  }
  else if (key == '@') {
    saveBlocksImages(true);
  }
}


// METHODS --------------------------------------------------

function makeCrack() {
  if (num<maxnum) {
    // make a new crack instance
    cracks[num] = new Crack();
    num++;
  }
}


function begin() {
  // erase crack grid
  for (let y=0; y<dimy; y++) {
    for (let x=0; x<dimx; x++) {
      cgrid[y*dimx+x] = 10001;
    }
  }
  // make random crack seeds
  for (let k=0; k<16; k++) {
    let i = int(random(dimx*dimy-1));
    cgrid[i]=int(random(360));
  }

  // make just three cracks
  num=0;
  for (let k=0; k<3; k++) {
    makeCrack();
  }
  background(255);
}


// COLOR METHODS ----------------------------------------------------------------

// note: original code file built a color-table from the
// pollockShimmering.gif when run. this version caches
// the result to remove that depedency. - @dribnet

const goodcolor = ["#fff0d0","#ffc828","#ffe898","#f0c898","#ffffd0","#a07800","#e8c898","#f8e070","#fff0c8","#d0b080","#fff8d0","#f8e8e0","#ffd8b0","#d0b078","#f0d8c0","#f0d8d0","#ffffd8","#c8c098","#d0c058","#e0c8a8","#d8d0b0","#f8f8d0","#b0a098","#fff0c0","#f0e8b8","#986870","#fff8d8","#ffe8c8","#f0e8c0","#f0e8d8","#e0d8b8","#e0d0a0","#e0d0b0","#d0c8a0","#e8e8d8","#f0d898","#f8f0d8","#fff0a0","#f8e878","#ffe878","#e8c848","#e8b878","#f8e050","#585048","#ffffc8","#a8a078","#e8e098","#f8f0c8","#e8d8a8","#fff8c8","#f0f8a8","#f0f0c0","#ffe078","#f0e8c8","#e8e080","#ffe868","#d0c0a0","#f0e080","#ffe890","#e0c858","#f8e0b8","#f0e0b8","#d8c898","#fff0b8","#c8b078","#f8e8b8","#e8d8c8","#f0c868","#a09078","#fff8c0","#f0f0c8","#ffe8b0","#e8e0b0","#ffd028","#b09030","#f0f0d0","#c0c090","#f8e0c0","#d0b890","#c0b078","#b0b098","#a8a880","#f0e0c0","#e0e0b8","#585838","#d0d0c0","#382810","#383828","#b8b8b0","#c0b090","#98a0b8","#e0b080","#c8c8b8","#f8e0b0","#d8c070","#f8e8c0","#e0d098","#e0d8b0","#586868","#e8f0c0","#906848","#b08868","#e8b828","#ffe8c0","#b0b078","#e0e0b0","#686870","#e0d8a0","#a0a0a8","#e0a060","#685858","#ff9828","#c0a060","#905818","#f8f0b8","#a89868","#a89070","#b0a870","#f0f0e0","#a89848","#586858","#e0b850","#c8b060","#b09020","#e0e0c0","#906070","#a07078","#b88868","#f8e0d8","#c09888","#ffe8d0","#d8c0a0","#c0c0b0","#e8c880","#c8b8a0","#d0c8b0","#fff0e8","#e8f0e0","#f8b828","#384030","#302008","#505860","#d8c0b0","#f0e0b0","#ffd0b8","#a05810","#501000","#e8c078","#f8b888","#e8d050","#fff0d8","#f0d870","#984008","#805800","#e8e0c8","#b8b8a8","#f0e8a0","#102028","#708080","#d8c8a0","#b0b8b0","#ffd8a0","#582800","#d8c8b0","#fff098","#d0c8a8","#fff8b0","#687078","#f0d098","#607070","#484858","#787880","#983010","#fff8e0","#905048","#a82818","#603810","#f8f8f8","#fff0f8"];

function somecolor() {
  return color(random(goodcolor));
}


// OBJECTS -------------------------------------------------------

function Crack() {
  this.x = null;
  this.y = null;
  this.t = null; // direction of travel in degrees

  this.findStart = function() {
    // pick random point
    let px=0;
    let py=0;

    // shift until crack is found
    let found=false;
    let timeout = 0;
    while ((!found) || (timeout++>1000)) {
      px = int(random(dimx));
      py = int(random(dimy));
      if (cgrid[py*dimx+px]<10000) {
        found=true;
      }
    }

    if (found) {
      // start crack
      let a = cgrid[py*dimx+px];
      if (random(100)<50) {
        a-=90+int(random(-2, 2.1));
      }
      else {
        a+=90+int(random(-2, 2.1));
      }
      this.startCrack(px, py, a);
    }
    else {
      //println("timeout: "+timeout);
    }
  }

  this.startCrack = function(X, Y, T) {
    this.x=X;
    this.y=Y;
    this.t=T;//%360;
    this.x+=0.61*cos(this.t*PI/180);
    this.y+=0.61*sin(this.t*PI/180);
  }

  this.move = function() {
    // continue cracking
    this.x+=0.42*cos(this.t*PI/180);
    this.y+=0.42*sin(this.t*PI/180); 

    // bound check
    let z = 0.33;
    let cx = int(this.x+random(-z, z));  // add fuzz
    let cy = int(this.y+random(-z, z));

    // draw sand painter
    this.regionColor();

    // draw black crack
    stroke(0, 85);
    point(this.x+random(-z, z), this.y+random(-z, z));


    if ((cx>=0) && (cx<dimx) && (cy>=0) && (cy<dimy)) {
      // safe to check
      if ((cgrid[cy*dimx+cx]>10000) || (abs(cgrid[cy*dimx+cx]-this.t)<5)) {
        // continue cracking
        cgrid[cy*dimx+cx]=int(this.t);
      }
      else if (abs(cgrid[cy*dimx+cx]-this.t)>2) {
        // crack encountered (not self), stop cracking
        this.findStart();
        makeCrack();
      }
    }
    else {
      // out of bounds, stop cracking
      this.findStart();
      makeCrack();
    }
  }

  this.regionColor = function() {
    // start checking one step away
    let rx=this.x;
    let ry=this.y;
    let openspace=true;

    // find extents of open space
    while (openspace) {
      // move perpendicular to crack
      rx+=0.81*sin(this.t*PI/180);
      ry-=0.81*cos(this.t*PI/180);
      let cx = int(rx);
      let cy = int(ry);
      if ((cx>=0) && (cx<dimx) && (cy>=0) && (cy<dimy)) {
        // safe to check
        if (cgrid[cy*dimx+cx]>10000) {
          // space is open
        }
        else {
          openspace=false;
        }
      }
      else {
        openspace=false;
      }
    }
    // draw sand painter
    this.sp.render(rx, ry, this.x, this.y);
  }

  // sand painter
  this.findStart();
  this.sp = new SandPainter();
}

function SandPainter() {
  this.c = somecolor();
  this.g = random(0.01, 0.1);

  this.render = function(x, y, ox, oy) {
    // modulate gain
    this.g+=random(-0.050, 0.050);
    let maxg = 1.0;
    if (this.g<0) {
      this.g=0;
    }
    if (this.g>maxg) {
      this.g=maxg;
    }

    // calculate grains by distance
    // let grains = int(sqrt((ox-x)*(ox-x)+(oy-y)*(oy-y)));
    let grains = 64;

    // lay down grains of sand (transparent pixels)
    let w = this.g/(grains-1);
    for (let i=0; i<grains; i++) {
      let a = 0.1-i/(grains*10.0);
      stroke(red(this.c), green(this.c), blue(this.c), a*256);
      point(ox+(x-ox)*sin(sin(i*w)), oy+(y-oy)*sin(sin(i*w)));
    }
  }
}

substrate.pde

// Substrate Watercolor
// j.tarbell   June, 2004
// Albuquerque, New Mexico
// complexification.net

// Processing 0085 Beta syntax update
// j.tarbell   April, 2005

int dimx = 500;
int dimy = 500;
int num = 0;
int maxnum = 150;

// grid of cracks
int[] cgrid;
Crack[] cracks;

// color parameters
int maxpal = 512;
int numpal = 0;
color[] goodcolor = new color[maxpal];

// sand painters
SandPainter[] sands;

// MAIN METHODS ---------------------------------------------

void setup() {
  size(500,500,P3D);
//  size(dimx,dimy,P3D);
  background(255);
  takecolor("pollockShimmering.gif");
  
  cgrid = new int[dimx*dimy];
  cracks = new Crack[maxnum];
  
  begin();  
}

void draw() {
  // crack all cracks
  for (int n=0;n<num;n++) {
    cracks[n].move();
  }
}

void mousePressed() {
  begin();
}
    

// METHODS --------------------------------------------------

void makeCrack() {
  if (num<maxnum) {
    // make a new crack instance
    cracks[num] = new Crack();
    num++;
  }
}


void begin() {
  // erase crack grid
  for (int y=0;y<dimy;y++) {
    for (int x=0;x<dimx;x++) {
      cgrid[y*dimx+x] = 10001;
    }
  }
  // make random crack seeds
  for (int k=0;k<16;k++) {
    int i = int(random(dimx*dimy-1));
    cgrid[i]=int(random(360));
  }

  // make just three cracks
  num=0;
  for (int k=0;k<3;k++) {
    makeCrack();
  }
  background(255);
}



// COLOR METHODS ----------------------------------------------------------------

color somecolor() {
  // pick some random good color
  return goodcolor[int(random(numpal))];
}

void takecolor(String fn) {
  PImage b;
  b = loadImage(fn);
  image(b,0,0);

  for (int x=0;x<b.width;x++){
    for (int y=0;y<b.height;y++) {
      color c = get(x,y);
      boolean exists = false;
      for (int n=0;n<numpal;n++) {
        if (c==goodcolor[n]) {
          exists = true;
          break;
        }
      }
      if (!exists) {
        // add color to pal
        if (numpal<maxpal) {
          goodcolor[numpal] = c;
          numpal++;
        }
      }
    }
  }
}




// OBJECTS -------------------------------------------------------

class Crack {
  float x, y;
  float t;    // direction of travel in degrees
  
  // sand painter
  SandPainter sp;
  
  Crack() {
    // find placement along existing crack
    findStart();
    sp = new SandPainter();
  }
  
  void findStart() {
    // pick random point
    int px=0;
    int py=0;
    
    // shift until crack is found
    boolean found=false;
    int timeout = 0;
    while ((!found) || (timeout++>1000)) {
      px = int(random(dimx));
      py = int(random(dimy));
      if (cgrid[py*dimx+px]<10000) {
        found=true;
      }
    }
    
    if (found) {
      // start crack
      int a = cgrid[py*dimx+px];
      if (random(100)<50) {
        a-=90+int(random(-2,2.1));
      } else {
        a+=90+int(random(-2,2.1));
      }
      startCrack(px,py,a);
    } else {
      //println("timeout: "+timeout);
    }
  }
   
  void startCrack(int X, int Y, int T) {
    x=X;
    y=Y;
    t=T;//%360;
    x+=0.61*cos(t*PI/180);
    y+=0.61*sin(t*PI/180);  
  }
             
  void move() {
    // continue cracking
    x+=0.42*cos(t*PI/180);
    y+=0.42*sin(t*PI/180); 
    
    // bound check
    float z = 0.33;
    int cx = int(x+random(-z,z));  // add fuzz
    int cy = int(y+random(-z,z));
    
    // draw sand painter
    regionColor();
    
    // draw black crack
    stroke(0,85);
    point(x+random(-z,z),y+random(-z,z));
    
    
    if ((cx>=0) && (cx<dimx) && (cy>=0) && (cy<dimy)) {
      // safe to check
      if ((cgrid[cy*dimx+cx]>10000) || (abs(cgrid[cy*dimx+cx]-t)<5)) {
        // continue cracking
        cgrid[cy*dimx+cx]=int(t);
      } else if (abs(cgrid[cy*dimx+cx]-t)>2) {
        // crack encountered (not self), stop cracking
        findStart();
        makeCrack();
      }
    } else {
      // out of bounds, stop cracking
      findStart();
      makeCrack();
    }
  }
  
  void regionColor() {
    // start checking one step away
    float rx=x;
    float ry=y;
    boolean openspace=true;
    
    // find extents of open space
    while (openspace) {
      // move perpendicular to crack
      rx+=0.81*sin(t*PI/180);
      ry-=0.81*cos(t*PI/180);
      int cx = int(rx);
      int cy = int(ry);
      if ((cx>=0) && (cx<dimx) && (cy>=0) && (cy<dimy)) {
        // safe to check
        if (cgrid[cy*dimx+cx]>10000) {
          // space is open
        } else {
          openspace=false;
        }
      } else {
        openspace=false;
      }
    }
    // draw sand painter
    sp.render(rx,ry,x,y);
  }
}


class SandPainter {

  color c;
  float g;

  SandPainter() {

    c = somecolor();
    g = random(0.01,0.1);
  }
  void render(float x, float y, float ox, float oy) {
    // modulate gain
    g+=random(-0.050,0.050);
    float maxg = 1.0;
    if (g<0) g=0;
    if (g>maxg) g=maxg;
    
    // calculate grains by distance
    //int grains = int(sqrt((ox-x)*(ox-x)+(oy-y)*(oy-y)));
    int grains = 64;

    // lay down grains of sand (transparent pixels)
    float w = g/(grains-1);
    for (int i=0;i<grains;i++) {
      float a = 0.1-i/(grains*10.0);
      stroke(red(c),green(c),blue(c),a*256);
      point(ox+(x-ox)*sin(sin(i*w)),oy+(y-oy)*sin(sin(i*w)));
    }
  }
}

// j.tarbell   June, 2004
// Albuquerque, New Mexico
// complexification.net

/** ----------------- 8< ----------------

    notes by dribnet july 2021

    Full information about Jared Tarbell's substrate found here:

    http://www.complexification.net/gallery/machines/substrate/

    This pde file was originally published online here:

    http://complexification.net/gallery/machines/substrate/appletm/substrate_m.pde

    There are two other versions posted, but these three
    differ only by resolution and maxnum:

       * small:  250x250, maxnum=100
       * medium: 500x500, maxnum=150
       * large:  900x900, maxnum=200

    Jared has no license but is very liberal with permissions,
    as is stated here:

    http://www.complexification.net/medium.html

    "I believe all code is dead unless executing within the computer.
    For this reason I distribute the source code of my programs in
    modifiable form. Modifications and extensions of these algorithms
    are encouraged."

    ----------------- 8< ---------------- **/

z_purview_helper.js

// helper functions below for supporting blocks/purview/screencap

function saveBlocksImages(doZoom) {
  if(doZoom == null) {
    doZoom = false;
  }

  // generate 960x500 preview.jpg of entire canvas
  // TODO: should this be recycled?
  var offscreenCanvas = document.createElement('canvas');
  offscreenCanvas.width = 960;
  offscreenCanvas.height = 500;
  var context = offscreenCanvas.getContext('2d');
  // background is flat white
  context.fillStyle="#FFFFFF";
  context.fillRect(0, 0, 960, 500);
  context.drawImage(this.canvas, 0, 0, 960, 500);
  // save to browser
  var downloadMime = 'image/octet-stream';
  var imageData = offscreenCanvas.toDataURL('image/jpeg');
  imageData = imageData.replace('image/jpeg', downloadMime);
  p5.prototype.downloadFile(imageData, 'preview.jpg', 'jpg');

  // generate 230x120 thumbnail.png centered on mouse
  offscreenCanvas.width = 230;
  offscreenCanvas.height = 120;

  // background is flat white  
  context = offscreenCanvas.getContext('2d');
  context.fillStyle="#FFFFFF";
  context.fillRect(0, 0, 230, 120);

  if(doZoom) {
    // pixelDensity does the right thing on retina displays
    var pd = this._pixelDensity;
    var sx = pd * mouseX - pd * 230/2;
    var sy = pd * mouseY - pd * 120/2;
    var sw = pd * 230;
    var sh = pd * 120;
    // bounds checking - just displace if necessary
    if (sx < 0) {
      sx = 0;
    }
    if (sx > this.canvas.width - sw) {
      sx = this.canvas.width - sw;
    }
    if (sy < 0) {
      sy = 0;
    }
    if (sy > this.canvas.height - sh) {
      sy = this.canvas.height - sh;
    }
    // save to browser
    context.drawImage(this.canvas, sx, sy, sw, sh, 0, 0, 230, 120);
  }
  else {
    // now scaledown
    var full_width = this.canvas.width;
    var full_height = this.canvas.height;
    context.drawImage(this.canvas, 0, 0, full_width, full_height, 0, 0, 230, 120);
  }
  imageData = offscreenCanvas.toDataURL('image/png');
  imageData = imageData.replace('image/png', downloadMime);
  // call this function after 1 second
  setTimeout(function(){
    p5.prototype.downloadFile(imageData, 'thumbnail.png', 'png');
  }, 1000);  
}