Ode to Fried Egg

Endless graphic repetition of fried eggs.

import * as random from "/scratchpad/_lib/random.asset.mjs"
import posterize from "/scratchpad/posterize/posterize.asset.mjs"

const repeat = (length, fn) => {
  Array.from({ length }).forEach((t, i) => {
    fn(i, length)
  })
}

const shape = (ctx, r, dir = 1) => {
  const x = random.wobble(4)
  const y = random.wobble(4)

  let r1 = r * random.number(1, 1.2)
  let r2 = r * random.number(0.7, 1)

  let variance = Math.abs(r2 / r1)
  variance = Math.min(variance, 1 / variance)

  if (variance < 0.2) {
    ctx.arc(0, 0, Math.abs(r2), 0, Math.PI * 2, dir < 0)
  } else {
    let points = random.integer(8, 19)

    for (let i = 0; i < points; i++) {
      const angle =
        random.number(Math.PI) +
        ((i * 2 * Math.PI) / (points % 2 == 0 ? points : points - 1)) * dir

      r = i % 2 === 0 ? r1 : r2

      ctx[i === 0 ? "moveTo" : "lineTo"](
        x + r * Math.cos(angle),
        y + r * Math.sin(angle)
      )
    }
  }
}

const palette = () => ({
  white: "white",
  yolk: [random.integer(220, 225), random.integer(128, 158), 0].reduce(
    (m, d) => m + ("0" + d.toString(16)).slice(-2),
    "#"
  ),
})

export default (ctx, dimensions, smooth = true) => {
  const { x, y, width, height } = dimensions

  const size = Math.min(width, height)
  const colors = palette()

  ctx.save()
  ctx.translate(x + width / 2, y + height / 2)

  ctx.beginPath()
  repeat(random.integer(50, 70), (i, length) => {
    const r = (size * 0.4 * (i + 1)) / length
    shape(ctx, r, i % 2 ? 1 : -1)
  })
  ctx.fillStyle = colors.white
  ctx.fill()

  ctx.beginPath()
  repeat(random.integer(20, 50), (i, length) => {
    const r = ((size / 2) * 0.4 * (i + 1)) / length
    shape(ctx, r, i % 2 ? 1 : -1)
  })
  ctx.fillStyle = colors.yolk
  ctx.fill()

  if (smooth) {
    posterize({
      ctx,
      radius: size * 0.04,
      iterations: 3,
      ramp: 300,
      colors: [colors.yolk, colors.white],
      overshoot: 1,
      x,
      y,
      width,
      height,
    })
  }

  ctx.restore()

  return { colors }
}