Grafik

Bursts of graffiti, a chaotic array of forms and figures.

Loading...
import helpers from "/scratchpad/_lib/line.asset.mjs"
import sequence from "/scratchpad/_lib/sequence.asset.mjs"
import posterize from "/scratchpad/posterize/posterize.asset.mjs"
import badge from "/scratchpad/_lib/badge.asset.mjs"
import palette from "/scratchpad/_lib/palette.asset.mjs"
import * as random from "/scratchpad/_lib/random.asset.mjs"

const dpi = window.devicePixelRatio || 1

export default (canvas) => {
  const scale = dpi

  canvas.width = canvas.offsetWidth * scale
  canvas.height = canvas.offsetHeight * scale

  const ctx = canvas.getContext("2d")
  const colors = [palette.dark, palette.canvas, palette.light]

  const gridSize = random.integer(4, 8)
  const strokeWidth = random.integer(20, 60)
  const iterations = random.integer(6, 10)
  const coverage = random.number(0.02)

  const offset = strokeWidth * 1.1
  const blurRadius = strokeWidth / 8

  let points = null

  const iteration = (index) => [
    () => {
      points = []

      const ow = canvas.width - offset * 2
      const oh = canvas.height - offset * 2

      helpers
        .makeGrid(ow, oh, gridSize)
        .filter(() => random.maybe(coverage))
        .forEach((point) => {
          const t = random.number(Math.PI)
          const x = Math.cos(t) * (gridSize / 3)
          const y = Math.sin(t) * (gridSize / 3)

          points.push({
            x: offset + point.x - x,
            y: offset + point.y - y,
          })
          points.push({
            x: offset + point.x + x,
            y: offset + point.y + y,
          })
        })
    },

    () => {
      const t = 1 - (iterations - index) / (iterations * 10)

      const remaining = [...points].filter((p) => random.maybe(0.6))

      ctx.beginPath()

      const fn = function (p0) {
        if (remaining.length < 4) return
        const [p1, p2] = helpers.nearest(remaining, p0).slice(1)

        ctx.moveTo(p1.x, p1.y)

        const mx = p1.x + (p2.x - p1.x) / 2 + random.wobble(canvas.width) / 40
        const my = p1.y + (p2.y - p1.y) / 2 + random.wobble(canvas.width) / 40
        ctx.quadraticCurveTo(mx, my, p2.x, p2.y)
      }

      random.shuffle(points).forEach(fn)

      ctx.globalCompositeOperation = "source-atop"
      ctx.globalAlpha = 0.2
      ctx.lineCap = "round"
      ctx.lineWidth = strokeWidth * t * 1.5
      ctx.strokeStyle = palette.dark
      ctx.stroke()

      ctx.globalCompositeOperation = "source-over"
      ctx.globalAlpha = 1
      ctx.lineWidth = strokeWidth * t
      ctx.strokeStyle = palette.dark
      ctx.stroke()

      ctx.strokeStyle = palette.canvas
      ctx.lineWidth = strokeWidth * t * 0.4
      ctx.stroke()

      if (random.maybe(0.9)) {
        ctx.save()
        ctx.translate(0, 80)
        ctx.strokeStyle = palette.dark
        ctx.lineWidth = strokeWidth * t * 0.1
        ctx.stroke()
        ctx.restore()
      }
    },

    () => {
      posterize({
        ctx,
        radius: blurRadius,
        colors,
        ramp: blurRadius * 2,
      })
    },
  ]

  const instructions = Array.from({ length: iterations }, (e, i) =>
    iteration(i)
  ).flat()

  instructions.push(() => badge(ctx, { stroke: false }))

  return sequence(instructions)
}