Chanomic Sketch

A Memory

"use strict";

let colBg;
let palette;
let lineDrawer;
let maskImg;
let scrambleCount;

function setup() {
  pixelDensity(1);
  createCanvas(windowWidth, windowHeight);

  const paletteBase = ['#61876E', '#A6BB8D', '#EAE7B1'].map((c) => {
    const col = color(c);
    col.setAlpha(128);
    return col;
  });
  palette = duplicate([5,1,1], paletteBase);

  colBg = color('#3C6255');
  scrambleCount = 3;

  maskImg = createGraphics(width, height);
  maskImg.fill(colBg);
  maskImg.noStroke();
  drawHill(maskImg);

  lineDrawer = new LineDrawer();

  background(colBg);
  fill(255, 192);
  noStroke();
  drawCircles(width/8, width/2, 10);
  image(maskImg, 0, 0);
}

function draw() {
  lineDrawer.draw();

  if (lineDrawer.x >= width) {
    if (scrambleCount > 0) {
      scramble();
      scrambleCount--;
    } else {
      noLoop();
    }
  }
}

class LineDrawer {
  constructor() {
    this.x = 0;
  }

  draw() {
    if (this.x >= width) {
      return;
    }
    const y = searchY(maskImg, this.x);
    const weight = random(1, 5);
    const dy = height - y;
    const [r1, r2] = [random(), random()].sort((a, b) => a - b);
    const cx1 = this.x + random(-50, 50);
    const cy1 = y + r1*dy;
    const cx2 = this.x + random(-50, 50);
    const cy2 = y + r2*dy;

    noFill();
    stroke(random(palette));
    strokeWeight(weight);
    bezier(this.x, y, cx1, cy1, cx2, cy2, this.x, height);
    this.x += weight/4;
  }
}

const drawCircles = (rMax, rMin, N) => {
  for (let i = 0; i < N; i++) {
    const x = random(0, width);
    const y = random(0, height);
    const r = random(rMax, rMin);
    circle(x, y, r);
  }
}

const searchY = (maskImg, x) => {
  for (let y = height-1; y >= 0; y--) {
    const [, , , a] = maskImg.get(x, y);
    if (a === 0) {
      return y;
    }
  }
}

const drawHill = (img) => {
  img.beginShape();
  img.vertex(-width/2, height/4*3);
  img.quadraticVertex(width/2, height/2, width/2*3, height/2 + height/8)
  img.vertex(width/2*3, height);
  img.vertex(-width/2, height);
  img.endShape();
}


const scramble = () => {
  loadPixels();
  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
      const i = (x + y*width)*4;
      const [dx, dy] = [random(-4, 4), random(-4, 4)].map((e) => floor(e));
      if (innerCanvas(x + dx, y + dy)) {
        const j = ((x + dx) + (y + dy)*width)*4;
        if (random() < 0.5) {
          swapPixels(pixels, i, j);
        }
      }
    }
  }
  updatePixels();
};

const swapPixels = (pixels, i, j) => {
  [pixels[i + 0], pixels[j + 0]] = [pixels[j + 0], pixels[i + 0]];
  [pixels[i + 1], pixels[j + 1]] = [pixels[j + 1], pixels[i + 1]];
  [pixels[i + 2], pixels[j + 2]] = [pixels[j + 2], pixels[i + 2]];
  [pixels[i + 3], pixels[j + 3]] = [pixels[j + 3], pixels[i + 3]];
}

const innerCanvas = (x, y) => {
  return 0 <= x && x < width && 0 <= y && y < height;
}

const duplicate = (counts, arr) => {
  const res = [];
  counts.forEach((n, i) => {
    for (let j = 0; j < n; j++) {
      res.push(arr[i]);
    }
  });
  return res;
}