REVDANCATT'S GENUARY 2023

06 - Steal Like An Artist

/* global preloadImagesTmr fxhash fxrand palettes StackBlur */

//
//  fxhash - Genuary 06 Steal Like An Artist - Giant Giant Steps - rudxane
//  Here I am stealing from rudxane's Giant Steps: https://www.fxhash.xyz/generative/12643
//  But also inspiration from his other projects, Tych (https://www.fxhash.xyz/generative/2675)
//  and Disrupt (https://www.fxhash.xyz/generative/6306).
//
//  The colour palettes are "stolen" from https://studioyorktown.github.io/coloryorktownhall/
//
//
//  HELLO!! Code is copyright revdancatt (that's me), so no sneaky using it for your
//  NFT projects.
//  But please feel free to unpick it, and ask me questions. A quick note, this is written
//  as an artist, which is a slightly different (and more storytelling way) of writing
//  code, than if this was an engineering project. I've tried to keep it somewhat readable
//  rather than doing clever shortcuts, that are cool, but harder for people to understand.
//
//  You can find me at...
//  https://twitter.com/revdancatt
//  https://instagram.com/revdancatt
//  https://youtube.com/revdancatt
//

const ratio = 4 / 3
// const startTime = new Date().getTime() // so we can figure out how long since the scene started
let drawn = false
let highRes = false // display high or low res
let clean = false
let animated = true
const features = {}
let resizeTmr = null
let drawTmr = null
const dumpOutputs = false
const v = 1000 * 60 * 10
const n = 1000 * 60
const f = 333
let speed = v
let rewindRestore = 0

window.$fxhashFeatures = {}

const hexToRgb = (hex) => {
  const result = /([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
  return {
    r: parseInt(result[1], 16),
    g: parseInt(result[2], 16),
    b: parseInt(result[3], 16)
  }
}

const rgbToHsl = (rgb) => {
  rgb.r /= 255
  rgb.g /= 255
  rgb.b /= 255
  const max = Math.max(rgb.r, rgb.g, rgb.b)
  const min = Math.min(rgb.r, rgb.g, rgb.b)
  let h
  let s
  const l = (max + min) / 2

  if (max === min) {
    h = s = 0 // achromatic
  } else {
    const d = max - min
    s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
    switch (max) {
      case rgb.r:
        h = (rgb.g - rgb.b) / d + (rgb.g < rgb.b ? 6 : 0)
        break
      case rgb.g:
        h = (rgb.b - rgb.r) / d + 2
        break
      case rgb.b:
        h = (rgb.r - rgb.g) / d + 4
        break
    }
    h /= 6
  }

  return {
    h: h * 360,
    s: s * 100,
    l: l * 100
  }
}

//  Work out what all our features are
const makeFeatures = () => {
  // Pick a random palette
  const rejectList = ['Ochre Comfort', 'Sherbet', 'Checkov', 'Elevated Basic', 'Hair Car', 'Scarry', 'Sojiro']
  let paletteIndex = Math.floor(fxrand() * palettes.length)
  features.palette = JSON.parse(JSON.stringify(palettes[paletteIndex]))
  let escape = 0
  while ((features.palette.colors.length < 3 || rejectList.includes(palettes[paletteIndex].name)) && escape < 100) {
    paletteIndex = Math.floor(fxrand() * palettes.length)
    features.palette = palettes[paletteIndex]
    escape++
  }
  // Grab the colours and convert them into hsl, to find the lightest and darkest, and the average
  let lightestColourIndex = 0
  let darkestColourIndex = 0
  let lightestColour = features.palette.colors[lightestColourIndex].value
  let darkestColour = features.palette.colors[darkestColourIndex].value
  for (let i = 0; i < features.palette.colors.length; i++) {
    const thisColour = features.palette.colors[i].value
    const thisHsl = rgbToHsl(hexToRgb(thisColour))
    if (thisHsl.l > rgbToHsl(hexToRgb(lightestColour)).l) {
      lightestColourIndex = i
      lightestColour = thisColour
    }
    if (thisHsl.l < rgbToHsl(hexToRgb(darkestColour)).l) {
      darkestColour = thisColour
      darkestColourIndex = i
    }
  }

  features.colours = {
    background: rgbToHsl(hexToRgb('#333333')),
    lines: rgbToHsl(hexToRgb('#DDDDDD')),
    block: '#CCCCCC',
    blockOutline: 'black',
    dot: 'magenta',
    blockLines: '#CCCCCC',
    grrrrrrid: '#000000'
  }
  window.$fxhashFeatures.palette = 'Dependable'
  window.$fxhashFeatures.background = 'Slate'
  window.$fxhashFeatures.accent = 'Pale Rider'

  // One third of the time we'll use either the lightest or darkest colour as the background
  if (fxrand() < 0.8) {
    window.$fxhashFeatures.palette = palettes[paletteIndex].name
    window.$fxhashFeatures.background = features.palette.colors[darkestColourIndex].name
    window.$fxhashFeatures.accent = features.palette.colors[lightestColourIndex].name

    features.colours.background = rgbToHsl(hexToRgb(darkestColour))
    features.colours.lines = rgbToHsl(hexToRgb(lightestColour))
    features.palette.colors = features.palette.colors.filter((c) => c.value !== darkestColour)
    features.palette.colors = features.palette.colors.filter((c) => c.value !== lightestColour)
    // Make the rest of them random
    features.colours.block = features.palette.colors[Math.floor(fxrand() * features.palette.colors.length)].value
    features.colours.blockOutline = features.palette.colors[Math.floor(fxrand() * features.palette.colors.length)].value
    features.colours.dot = features.palette.colors[Math.floor(fxrand() * features.palette.colors.length)].value
    features.colours.blockLines = features.palette.colors[Math.floor(fxrand() * features.palette.colors.length)].value
    features.colours.grrrrrrid = features.palette.colors[Math.floor(fxrand() * features.palette.colors.length)].value
  }

  // Sometimes we'll offset things by a bit, this is how much we'll do that
  features.featureOffset = {
    x: 0.005,
    y: 0.005
  }
  features.blockOutlines = fxrand() < 0.7

  const tinyTiny = 1
  // We are going to have a random number of lines from 5 to 9 on each side plus 1
  const lineCount = ((Math.floor(fxrand() * 4) + 5) * 2 + 1) * tinyTiny
  // The lines run from -1 to 1, so we need to work out the step size
  features.step = 2 / (lineCount - 1)
  // Now make the lines
  features.lines = []
  for (let i = 0; i < lineCount; i++) {
    const thisLine = {
      p1: {
        x: -1 + (features.step * i),
        y: -2
      },
      p2: {
        x: -1 + (features.step * i),
        y: 2
      }
    }
    features.lines.push(thisLine)
  }
  // I want to bunch some of the ends up to bring them closer together
  const scaleFactor = fxrand() * 0.33 + 0.33
  for (let i = 0; i < features.lines.length; i++) features.lines[i].p2.x *= scaleFactor

  // Now we want to put blocks between the lines. So we'll need to create a step value
  // and then loop through the lines, ignoring the last one, and creating a block
  // between each line
  const maxBlocks = 35 * tinyTiny
  const blockSize = (features.lines[0].p2.y - features.lines[0].p1.y) / maxBlocks

  // If the block is a grrrrrrid, then we need to work out what type it is
  let gridTypes = ['dots', 'dotEdgeL', 'dotEdgeBoth', 'fuzzyDots', 'fluffySide', 'fluffyEnd']
  // Sometimes we want to pick one at random and make that the whole array
  if (fxrand() < 0.333) {
    gridTypes = [gridTypes[Math.floor(fxrand() * gridTypes.length)]]
  }

  features.blocks = []
  for (let i = 0; i < features.lines.length - 1; i++) {
    // Now loop through the number of blocks we have
    for (let b = 0; b < maxBlocks; b++) {
      // There is a chance to place a block here
      const newBlock = {
        firstLine: i,
        secondLine: i + 1,
        top: b * blockSize,
        bottom: (b + 1) * blockSize,
        type: 'dot',
        randoms: []
      }
      if (fxrand() < 0.3) newBlock.type = 'block'
      if (fxrand() < 0.3) newBlock.type = 'lines'
      if (fxrand() < 0.3) newBlock.type = 'grrrrrrid'
      // We also need to give each block around 10,000 random numbers in an array we can use
      for (let r = 0; r < 10000; r++) newBlock.randoms.push(fxrand())

      if (newBlock.type === 'grrrrrrid') {
        const choice = Math.floor(fxrand() * gridTypes.length)
        newBlock.gridType = gridTypes[choice]
      }
      newBlock.dotShowRandom = fxrand() < 0.2
      newBlock.dotRandomColour = fxrand() < 0.4

      features.blocks.push(newBlock)
    }
  }
  // Work out by what we're going to rotate the canvas by
  features.rotation = fxrand() * 360

  // Now list the features
  window.$fxhashFeatures['Block Outlines'] = features.blockOutlines
  window.$fxhashFeatures.Steppy = features.lines.length
  window.$fxhashFeatures.Blocks = features.blocks.filter((b) => b.type === 'block').length
  window.$fxhashFeatures.Disrupts = features.blocks.filter((b) => b.type === 'lines').length
  window.$fxhashFeatures.Bingos = features.blocks.filter((b) => b.type === 'grrrrrrid' && b.gridType === 'fuzzyDots').length
  window.$fxhashFeatures.Tychs = features.blocks.filter((b) => b.type === 'grrrrrrid' && (b.gridType === 'fluffyEnd' || b.gridType === 'fluffySide')).length
  window.$fxhashFeatures.Dotty = features.blocks.filter((b) => b.type === 'grrrrrrid').length - window.$fxhashFeatures.Bingos - window.$fxhashFeatures.Tychs
}

//  Call the above make features, so we'll have the window.$fxhashFeatures available
//  for fxhash
makeFeatures()

console.log(features)
console.table(window.$fxhashFeatures)

const init = async () => {
  //  I should add a timer to this, but really how often to people who aren't
  //  the developer resize stuff all the time. Stick it in a digital frame and
  //  have done with it!
  window.addEventListener('resize', async () => {
    //  If we do resize though, work out the new size...
    clearTimeout(resizeTmr)
    resizeTmr = setTimeout(async () => {
      await layoutCanvas()
    }, 100)
  })

  //  Now layout the canvas
  await layoutCanvas()
}

const layoutCanvas = async () => {
  const wWidth = window.innerWidth
  const wHeight = window.innerHeight
  let cWidth = wWidth
  let cHeight = cWidth * ratio
  if (cHeight > wHeight) {
    cHeight = wHeight
    cWidth = wHeight / ratio
  }
  const canvas = document.getElementById('target')
  if (highRes) {
    canvas.height = 8192
    canvas.width = 8192 / ratio
  } else {
    canvas.width = Math.min((8192 / 2), cWidth * 2)
    canvas.height = Math.min((8192 / ratio / 2), cHeight * 2)
    //  Minimum size to be half of the high rez cersion
    if (Math.min(canvas.width, canvas.height) < 8192 / 2) {
      if (canvas.width < canvas.height) {
        canvas.height = 8192 / 2
        canvas.width = 8192 / 2 / ratio
      } else {
        canvas.width = 8192 / 2
        canvas.height = 8192 / 2 / ratio
      }
    }
  }

  canvas.style.position = 'absolute'
  canvas.style.width = `${cWidth}px`
  canvas.style.height = `${cHeight}px`
  canvas.style.left = `${(wWidth - cWidth) / 2}px`
  canvas.style.top = `${(wHeight - cHeight) / 2}px`

  // fill the background
  const ctx = canvas.getContext('2d')
  ctx.fillStyle = `hsl(${features.colours.background.h}, ${features.colours.background.s}%, ${features.colours.background.l}%)`
  ctx.fillRect(0, 0, canvas.width, canvas.height)

  //  And draw it!!
  drawCanvas()
}

const scaleBlock = (block, scale) => {
  // translate all the corners by the middle
  // (We could do this all in a handy transformation matrix, but this way is somehow more readable)
  block.topLeft.x -= block.middle.x
  block.topLeft.y -= block.middle.y
  block.bottomLeft.x -= block.middle.x
  block.bottomLeft.y -= block.middle.y
  block.topRight.x -= block.middle.x
  block.topRight.y -= block.middle.y
  block.bottomRight.x -= block.middle.x
  block.bottomRight.y -= block.middle.y

  // Now we multiply all the points by the scale
  block.topLeft.x *= scale
  block.topLeft.y *= scale
  block.bottomLeft.x *= scale
  block.bottomLeft.y *= scale
  block.topRight.x *= scale
  block.topRight.y *= scale
  block.bottomRight.x *= scale
  block.bottomRight.y *= scale

  // Finally we now shift them all back
  block.topLeft.x += block.middle.x
  block.topLeft.y += block.middle.y
  block.bottomLeft.x += block.middle.x
  block.bottomLeft.y += block.middle.y
  block.topRight.x += block.middle.x
  block.topRight.y += block.middle.y
  block.bottomRight.x += block.middle.x
  block.bottomRight.y += block.middle.y

  return block
}

const drawCanvas = async () => {
  //  Let the preloader know that we've hit this function at least once
  drawn = true

  const canvas = document.getElementById('target')
  const ctx = canvas.getContext('2d')
  const w = canvas.width
  const h = canvas.height

  // fill the background
  // if (clean) {
  ctx.fillStyle = `hsl(${features.colours.background.h}, ${features.colours.background.s}%, ${features.colours.background.l}%)`
  ctx.fillRect(0, 0, w, h)
  // }
  // Now draw the lines
  ctx.strokeStyle = `hsl(${features.colours.lines.h}, ${features.colours.lines.s}%, ${features.colours.lines.l}%)`
  ctx.lineWidth = w / 500

  // save the canvas state
  ctx.save()
  // translate the canvas to the center
  ctx.translate(w / 2, h / 2)
  ctx.rotate((features.rotation * Math.PI) / 180)
  // Loop through the lines
  for (let i = 0; i < features.lines.length; i++) {
    const thisLine = features.lines[i]
    // Draw the line
    ctx.beginPath()
    ctx.moveTo(thisLine.p1.x * w, thisLine.p1.y * h)
    ctx.lineTo(thisLine.p2.x * w, thisLine.p2.y * h)
    ctx.stroke()
  }

  // Now loop thru the blocks
  for (let i = 0; i < features.blocks.length; i++) {
    const thisBlock = features.blocks[i]
    const firstLine = features.lines[thisBlock.firstLine]
    const secondLine = features.lines[thisBlock.secondLine]
    let rndPointer = 0

    // Now we need to work out the four corners of the block
    // The top left corner is block top distance between firstLine.p1 and firstLine.p2
    let corners = {
      topLeft: {
        x: firstLine.p1.x + (firstLine.p2.x - firstLine.p1.x) * (thisBlock.top / (firstLine.p2.y - firstLine.p1.y)),
        y: firstLine.p1.y + (firstLine.p2.y - firstLine.p1.y) * (thisBlock.top / (firstLine.p2.y - firstLine.p1.y))
      },
      bottomLeft: {
        x: firstLine.p1.x + (firstLine.p2.x - firstLine.p1.x) * (thisBlock.bottom / (firstLine.p2.y - firstLine.p1.y)),
        y: firstLine.p1.y + (firstLine.p2.y - firstLine.p1.y) * (thisBlock.bottom / (firstLine.p2.y - firstLine.p1.y))
      },
      topRight: {
        x: secondLine.p1.x + (secondLine.p2.x - secondLine.p1.x) * (thisBlock.top / (secondLine.p2.y - secondLine.p1.y)),
        y: secondLine.p1.y + (secondLine.p2.y - secondLine.p1.y) * (thisBlock.top / (secondLine.p2.y - secondLine.p1.y))
      },
      bottomRight: {
        x: secondLine.p1.x + (secondLine.p2.x - secondLine.p1.x) * (thisBlock.bottom / (secondLine.p2.y - secondLine.p1.y)),
        y: secondLine.p1.y + (secondLine.p2.y - secondLine.p1.y) * (thisBlock.bottom / (secondLine.p2.y - secondLine.p1.y))
      }
    }

    // Work out the middle point of the corners
    corners.middle = {
      x: corners.topLeft.x + ((corners.bottomRight.x - corners.topLeft.x) / 2),
      y: corners.topLeft.y + ((corners.bottomRight.y - corners.topLeft.y) / 2)
    }

    // Work out the middleTop point of the corners
    corners.middleTop = {
      x: corners.topLeft.x + ((corners.topRight.x - corners.topLeft.x) / 2),
      y: corners.topLeft.y + ((corners.topRight.y - corners.topLeft.y) / 2)
    }

    // Now we need to scale the block, but only if it's not lines
    if (thisBlock.type !== 'lines') corners = scaleBlock(corners, 0.8)

    // Now draw the block
    if (thisBlock.type === 'block') {
      ctx.fillStyle = features.colours.block
      ctx.strokeStyle = features.colours.blockOutline
      // First the solid block
      ctx.beginPath()
      ctx.moveTo(corners.topLeft.x * w, corners.topLeft.y * h)
      ctx.lineTo(corners.topRight.x * w, corners.topRight.y * h)
      ctx.lineTo(corners.bottomRight.x * w, corners.bottomRight.y * h)
      ctx.lineTo(corners.bottomLeft.x * w, corners.bottomLeft.y * h)
      ctx.lineTo(corners.topLeft.x * w, corners.topLeft.y * h)
      ctx.fill()
      // If we are drawing a block outline, then draw it
      // but offset the canvas so we use the same path
      if (features.blockOutlines) {
        ctx.lineWidth = w / 800
        ctx.beginPath()
        ctx.moveTo((corners.topLeft.x + features.featureOffset.x) * w, (corners.topLeft.y + features.featureOffset.y) * h)
        ctx.lineTo((corners.topRight.x + features.featureOffset.x) * w, (corners.topRight.y + features.featureOffset.y) * h)
        ctx.lineTo((corners.bottomRight.x + features.featureOffset.x) * w, (corners.bottomRight.y + features.featureOffset.y) * h)
        ctx.lineTo((corners.bottomLeft.x + features.featureOffset.x) * w, (corners.bottomLeft.y + features.featureOffset.y) * h)
        ctx.lineTo((corners.topLeft.x + features.featureOffset.x) * w, (corners.topLeft.y + features.featureOffset.y) * h)
        ctx.stroke()
      }
    }

    // If we are drawing a grrrrrrid, then we need to break down the block into 10 by 10 points
    if (thisBlock.type === 'grrrrrrid') {
      const points = 10
      for (let y = 0; y <= points; y++) {
        const yPercent = y / points
        for (let x = 0; x <= points; x++) {
          const xPercent = x / points
          const topMiddlePoint = {
            x: corners.topLeft.x + (corners.topRight.x - corners.topLeft.x) * xPercent,
            y: corners.topLeft.y + (corners.topRight.y - corners.topLeft.y) * xPercent
          }
          const bottomMiddlePoint = {
            x: corners.bottomLeft.x + (corners.bottomRight.x - corners.bottomLeft.x) * xPercent,
            y: corners.bottomLeft.y + (corners.bottomRight.y - corners.bottomLeft.y) * xPercent
          }
          const thisPoint = {
            x: topMiddlePoint.x + (bottomMiddlePoint.x - topMiddlePoint.x) * yPercent,
            y: topMiddlePoint.y + (bottomMiddlePoint.y - topMiddlePoint.y) * yPercent
          }

          ctx.fillStyle = features.colours.grrrrrrid
          ctx.strokeStyle = features.colours.grrrrrrid

          // If this is the fluffy side then only do things on the first x
          if (thisBlock.gridType === 'dots' || (thisBlock.gridType === 'dotEdgeL' && x === 0) || (thisBlock.gridType === 'dotEdgeBoth' && (x === 0 || x === points))) {
            // If this dot is a random colour then we need to pick a random colour
            if (thisBlock.dotRandomColour && thisBlock.randoms[rndPointer] > 0.9) {
              rndPointer++
              ctx.fillStyle = features.palette.colors[Math.floor(thisBlock.randoms[rndPointer] * features.palette.colors.length)].value
              rndPointer++
            }
            // If we are showing random dots, then we need to check if we should draw this dot
            // if we are not showing random dots, then we always draw the dot
            if (thisBlock.randoms[rndPointer] > 0.1 || !thisBlock.dotShowRandom) {
              ctx.beginPath()
              ctx.arc(thisPoint.x * w, thisPoint.y * h, w / 500, 0, 2 * Math.PI)
              ctx.fill()
            }
            rndPointer++
          }

          // If this is the fluffy side then only do things on the first x
          if (thisBlock.gridType === 'fuzzyDots' && x < points) {
            // We want to draw 20 lines randomly from this point to roughly a point half a block width across
            for (let i = 0; i < 20; i++) {
              ctx.lineWidth = w / 1000
              ctx.beginPath()
              ctx.moveTo(thisPoint.x * w, thisPoint.y * h)
              ctx.lineTo((thisPoint.x + (thisBlock.randoms[rndPointer + 0] * 0.01)) * w, (thisPoint.y + (thisBlock.randoms[rndPointer + 1] * 0.01)) * h)
              rndPointer += 2
              ctx.stroke()
            }
          }

          // If this is the fluffy side then only do things on the first x
          if (thisBlock.gridType === 'fluffySide' && x < 4) {
            // We want to draw 20 lines randomly from this point to roughly a point half a block width across
            for (let i = 0; i < 20; i++) {
              ctx.lineWidth = w / 1000
              ctx.beginPath()
              ctx.moveTo(thisPoint.x * w, thisPoint.y * h)
              ctx.lineTo((thisPoint.x + (thisBlock.randoms[rndPointer + 0] * 0.05)) * w, (thisPoint.y + (thisBlock.randoms[rndPointer + 1] * 0.01)) * h)
              rndPointer += 2
              ctx.stroke()
            }
          }

          // if type is fluffy end, then we need to draw a lines from the end of this block to this point
          if (thisBlock.gridType === 'fluffyEnd') {
            ctx.lineWidth = w / 1000
            ctx.beginPath()
            // do this 5 times
            for (let i = 0; i < 5; i++) {
              ctx.moveTo(corners.middleTop.x * w, (corners.middleTop.y + 0.01) * h)
              ctx.lineTo((thisPoint.x + (thisBlock.randoms[rndPointer + 0] * 0.01)) * w, (thisPoint.y + (thisBlock.randoms[rndPointer + 1] * 0.01)) * h)
              rndPointer += 2
            }
            ctx.stroke()
          }
        }
      }
    }

    // If we are drawing lines, then we'll draw 50 lines between the corners
    if (thisBlock.type === 'lines') {
      ctx.strokeStyle = features.colours.blockLines
      ctx.lineWidth = w / 2000
      ctx.beginPath()
      const lines = 50
      for (let l = 0; l < lines; l++) {
        let leftLinePercent = l / lines
        if (thisBlock.randoms[rndPointer] < 0.333) {
          if (leftLinePercent > thisBlock.randoms[rndPointer + 1] && leftLinePercent < thisBlock.randoms[rndPointer + 1] + (thisBlock.randoms[rndPointer + 2] * 0.2 + 0.1)) {
            leftLinePercent = thisBlock.randoms[rndPointer + 3] * 0.7 + 0.15
          }
        }
        let rightLinePercent = l / lines
        if (thisBlock.randoms[rndPointer + 4] < 0.333) {
          if (rightLinePercent > thisBlock.randoms[rndPointer + 5] && rightLinePercent < thisBlock.randoms[rndPointer + 5] + (thisBlock.randoms[rndPointer + 6] * 0.2 + 0.1)) {
            rightLinePercent = thisBlock.randoms[rndPointer + 7] * 0.7 + 0.15
          }
        }

        // work out the start point along the left side
        const leftPoint = {
          x: corners.topLeft.x + (corners.bottomLeft.x - corners.topLeft.x) * leftLinePercent,
          y: corners.topLeft.y + (corners.bottomLeft.y - corners.topLeft.y) * leftLinePercent
        }
        // work out the end point along the right side
        const rightPoint = {
          x: corners.topRight.x + (corners.bottomRight.x - corners.topRight.x) * rightLinePercent,
          y: corners.topRight.y + (corners.bottomRight.y - corners.topRight.y) * rightLinePercent
        }
        // Draw a line between the two points
        ctx.moveTo(leftPoint.x * w, leftPoint.y * h)
        // ctx.lineTo(rightPoint.x * w, rightPoint.y * h)
        ctx.bezierCurveTo(leftPoint.x * w, (leftPoint.y + 0.01) * h, rightPoint.x * w, (rightPoint.y + 0.01) * h, rightPoint.x * w, rightPoint.y * h)
      }
      rndPointer += 8
      ctx.stroke()
    }

    // Draw a red dot
    ctx.fillStyle = features.colours.dot
    ctx.beginPath()
    ctx.arc(corners.middle.x * w, corners.middle.y * h, w / 250, 0, 2 * Math.PI)
    // ctx.fill()
  }
  if (clean || rewindRestore > 33) {
    for (let i = 1; i < rewindRestore; i++) ctx.restore()
    rewindRestore = 0
    ctx.restore()
  } else {
    rewindRestore++
  }

  // Call the draw function again
  // aniFrame = window.requestAnimationFrame(drawCanvas)
  if (dumpOutputs) autoDownloadCanvas()
  // In 10 seconds we're going to call makeFeatures again, then drawCanvas again
  if (animated) {
    clearTimeout(drawTmr)
    drawTmr = setTimeout(async () => {
      await StackBlur.canvasRGBA(canvas, 0, 0, w, h, Math.min(w, h) / 1000)
      makeFeatures()
      drawCanvas()
    }, speed)
  }
}

const autoDownloadCanvas = async (showHash = false) => {
  const element = document.createElement('a')
  element.setAttribute('download', `Giant_Giant_Steps-rudxane-${fxhash}`)
  element.style.display = 'none'
  document.body.appendChild(element)
  let imageBlob = null
  imageBlob = await new Promise(resolve => document.getElementById('target').toBlob(resolve, 'image/png'))
  element.setAttribute('href', window.URL.createObjectURL(imageBlob, {
    type: 'image/png'
  }))
  element.click()
  document.body.removeChild(element)
  if (dumpOutputs) window.location.reload()
}

//  KEY PRESSED OF DOOM
document.addEventListener('keypress', async (e) => {
  e = e || window.event
  // Save
  if (e.key === 's') autoDownloadCanvas()

  //   Toggle highres mode
  if (e.key === 'h') {
    highRes = !highRes
    console.log('Highres mode is now', highRes)
    await layoutCanvas()
  }

  // Toggle clean mode
  if (e.key === 'c') {
    clean = !clean
    console.log('Clean mode is now', clean)
    await layoutCanvas()
    if (!clean) {
      clearTimeout(drawTmr)
      drawTmr = setTimeout(() => {
        makeFeatures()
        drawCanvas()
      }, 333)
    } else {
      makeFeatures()
      drawCanvas()
    }
  }

  // Toggle animated mode
  if (e.key === 'a') {
    animated = !animated
    console.log('animated mode is now', animated)
    clearTimeout(drawTmr)
    if (animated) {
      drawTmr = setTimeout(() => {
        makeFeatures()
        drawCanvas()
      }, speed)
    }
  }

  if (e.key === 'f') {
    speed = f
    console.log('Mode is now fast')
    animated = true
    console.log('animated mode is now', animated)
    clearTimeout(drawTmr)
    drawTmr = setTimeout(() => {
      makeFeatures()
      drawCanvas()
    }, speed)
  }

  if (e.key === 'n') {
    speed = n
    console.log('Mode is now normal')
    animated = true
    console.log('animated mode is now', animated)
    clearTimeout(drawTmr)
    drawTmr = setTimeout(() => {
      makeFeatures()
      drawCanvas()
    }, speed)
    makeFeatures()
    drawCanvas()
  }

  if (e.key === 'v') {
    speed = v
    console.log('Mode is now very slow')
    animated = true
    console.log('animated mode is now', animated)
    clearTimeout(drawTmr)
    drawTmr = setTimeout(() => {
      makeFeatures()
      drawCanvas()
    }, speed)
    makeFeatures()
    drawCanvas()
  }
})

//  This preloads the images so we can get access to them
// eslint-disable-next-line no-unused-vars
const preloadImages = () => {
  if (!drawn) {
    clearInterval(preloadImagesTmr)
    init()
  }
}

Project files


Home
Changelog
Page created in: 7ms