<script type="text/js-worker">
const getNewPixel = valid =>
  Array.from(new Array(4), (_1, index) => undefined).map((_2, pIndex, m) => {
    const value = valid.reduce((acc, v) => acc + v[pIndex], 0)
    return ~~(value / valid.length)
  })

const redraw = d => {
  self.postMessage({ type: "redrawing", isRedrawing: true })
  const ctx = canvas.getContext("2d")
  let currentProgress = 50
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  d.forEach((r, yi, selfY) =>
    r.forEach((p, xi, selfX) => {
      ctx.fillStyle = `rgba(${p[0]}, ${p[1]}, ${p[2]}, ${p[3]})`
      ctx.fillRect(xi, yi, 1, 1)

      const progress = ~~(
        50 +
        ((yi * selfX.length + xi) / (selfY.length * selfX.length)) * 50
      )

      if (currentProgress !== progress) {
        currentProgress = progress
        self.postMessage({ type: "progress", progress })
      }
    })
  )
  self.postMessage({ type: "redrawing", isRedrawing: false })
}

drawPixel = (p, x, y) => new Promise(res => {
    const ctx = canvas.getContext("2d")
    ctx.fillStyle = `rgba(${p[0]}, ${p[1]}, ${p[2]}, ${p[3]})`
    ctx.fillRect(x, y, 1, 1)
    res()
})

const blur = async data => {
  let currentProgress = 0
  
  let yi = 0, xi = 0, temp=[]
  
  for (let line of data) {
    xi = 0
    for (let pixel of line) {
      const top2 = data[yi - 1]
      const top1 = data[yi - 1]
      const mid = line
      const bot1 = data[yi + 1]
      const bot2 = data[yi + 1]
      const round = [
        ...(top2 !== undefined
          ? [top2[xi - 2], top2[xi - 1], top2[xi], top2[xi + 1], top2[xi + 2]]
          : []),
        ...(top1 !== undefined
          ? [top1[xi - 2], top1[xi - 1], top1[xi], top1[xi + 1], top1[xi + 2]]
          : []),
        ...(mid !== undefined
          ? [mid[xi - 2], mid[xi - 1], mid[xi], mid[xi + 1], mid[xi + 2]]
          : []),
        ...(bot1 !== undefined
          ? [bot1[xi - 2], bot1[xi - 1], bot1[xi], bot1[xi + 1], bot1[xi + 2]]
          : []),
        ...(bot2 !== undefined
          ? [bot2[xi - 2], bot2[xi - 1], bot2[xi], bot2[xi + 1], bot2[xi + 2]]
          : [])
      ]

      const valid = round.filter(v => v)

      const np = valid.length > 0 ? getNewPixel(valid) : line[xi]
      
      await drawPixel(np, xi, yi)
      
      temp.push(np)
      

      const progress = ~~(
        ((yi * line.length + xi) / (line.length * data.length)) * 100
      )

      if (currentProgress !== progress) {
        currentProgress = progress
        self.postMessage({ type: "progress", progress })
        await new Promise(res => setTimeout(() => res(), 0))
      }
      xi++
    }
    pixels[yi] = temp
    temp = []
    yi++
  }
}

const changeBrightness = async (data, value = 0) => {
  let currentProgress = 0
  
  let yi = 0, xi = 0, temp=[]
  
  for (let line of data) {
    xi = 0
    for (let pixel of line) {
      const np = [pixel[0] + value, pixel[1] + value, pixel[2] + value, pixel[3]]
      
      const progress = ~~(
        ((yi * line.length + xi) / (line.length * data.length)) * 100
      )
      await drawPixel(np, xi, yi)
      
      if (currentProgress !== progress) {
        currentProgress = progress
        self.postMessage({ type: "progress", progress })
        await new Promise(res => setTimeout(() => res(), 0))
      }
      pixels[yi][xi] = np
      
      xi++
    }
    yi++
  }
}

const getImageData = async () => {
  canvas.width = image.width
  canvas.height = image.height

  const ctx = canvas.getContext("2d")

  ctx.drawImage(image, 0, 0)
  
  return new Promise(async res => {
    setTimeout(() => {
      let currentProgress = 0

      imageData = ctx.getImageData(0, 0, image.width, image.height)
      res(
        Array.from(new Array(imageData.height), (_, y) =>
          Array.from(new Array(imageData.width), (_, x) => {
            const s = y * imageData.width * 4 + x * 4
            const progress = ~~(
              ((y * imageData.width + x) /
                (imageData.height * imageData.width)) *
              100
            )
            if (currentProgress !== progress) {
              currentProgress = progress
              self.postMessage({ type: "progress", progress })
            }
            return imageData.data.slice(s, s + 4)
          })
        )
      )
    })
  })
}

let canvas, image, imageData, pixels, pixelRatio

self.onmessage = async ({ data }) => {
  try {
    const { type } = data
    if (type === "canvas") {
      canvas = data.canvas
      const ctx = canvas.getContext("2d")
      ctx.font = "30px Arial";
      ctx.fillText("Hello From Worker", 20, 50);
      pixelRatio = data.pixelRatio
    } else if (type === "image") {
      image = data.image
      pixels = await getImageData()
      self.postMessage({ type: "onEnd" })
    } else if (type === "brightness") {
      await changeBrightness(pixels, parseInt(data.value, 10))
      self.postMessage({ type: "onEnd" })
    } else if (type === "blur") {
      await blur(pixels)
      self.postMessage({ type: "onEnd" })
    }
  } catch (error) {
    console.error(error)
    self.postMessage("Error:", error.message)
  }
}

</script>

<input onchange="handleImageUpload(this.files)" type="file" />

<div id="setting" class="setting">0</div>
<input type="range" min="-255" max="255" value="0" onchange="handleBrightnessChange(this.value)">

<button onClick="handleBrightness()">Change Brightness</button>

<button onClick="handleBlur()">Blur</button>

<div class="progressContainer">
  <div>Progress:</div>
  <div class="barContainer">
    <div class="progressBar" id="progressBar" style="transform: scaleX(0);"> </div>
  </div>
</div>

<canvas id="imageCanvas"></canvas>


<div class="animations">
  <div class="transformAnimation"></div>
  <div class="marginAnimation"></div>
</div>
<!-- 
<button onclick="initWorkerLoad()">Get me some primes!</button>

<br />
Animated with CSS
<div class="animations">
  <div class="transformAnimation"></div>
  <div class="marginAnimation"></div>
</div>

Animated with JS
<div class="animations">
  <div class="static" id="static" style={{ transform: `translate(${this.state.margin}px)` }}></div>
  </div>
</div>
</div> -->
.progressContainer {
  width: 600px;
  padding: 20px;
  margin: 0px;
  .barContainer {
    border-radius: 5px;
    overflow: hidden;
    border: 1px solid #aaa;
    width: 100%;
    height: 20px;
    margin: 0;
    .progressBar {
      margin: 0;
      width: 100%;
      height: 100%;
      background-color: #09f;
      transform-origin: left;
      transition: 0.1s;
    }
  }
}

.setting {
  display: inline-block;
  width: 40px;
}

.animations {
  background-color: "red";
  width: 300px;
  height: 40px;
  position: relative;
}
  .transformAnimation,
  .marginAnimation,
  .static {
    position: absolute;
    left: 0;
    width: 20px;
    height: 20px;
    border-radius: 5px;
    margin: 0;
  }
  .transformAnimation {
    top: 0px;
    background-color: #55f;
    animation: transformLeft 4s infinite linear;
  }
  .marginAnimation {
    top: 20px;
    background-color: #f55;
    animation: marginLeft 2s infinite linear;
  }
  .static {
    top: 30px;
    background-color: #555;
  }

@keyframes transformLeft {
  0% {
    transform: translate(0px);
  }
  100% {
    transform: translate(300px);
  }
}

@keyframes marginLeft {
  from {
    margin-left: 0px;
  }
  to {
    margin-left: 300px;
  }
}

@keyframes rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
View Compiled
var blob = new Blob(
  Array.prototype.map.call(
    document.querySelectorAll('script[type="text\/js-worker"]'),
    function (oScript) { return oScript.textContent; }
  ),
  { type: 'text/javascript' }
)

// Create a new worker containing all "text/js-worker" scripts
const worker = new Worker(window.URL.createObjectURL(blob))

// Listen for a message from the worker
worker.addEventListener('message', e => {
  const { type, progress } = e.data
  if (type === "progress") {
    document.getElementById("progressBar").style.transform = `scaleX(${progress / 100})`
  } else if (type === "onEnd") {
    document.getElementById("progressBar").style.transform = 'scaleX(0)'
  }
  // appendToList(e.data[e.data.length - 1])
})

// hand off canvas control to worker
const offscreenControl = document.getElementById('imageCanvas').transferControlToOffscreen()
worker.postMessage(
  {
    type: "canvas",
    canvas: offscreenControl,
    pixelRatio: window.devicePixelRatio
  },
  [offscreenControl]
)

appendToList = (content) => {
  // Create a <li> node
  let node = document.createElement("LI")
  // Append the text to <li>
  node.appendChild(document.createTextNode(content))
  document.getElementById("primes").appendChild(node);  
}

//  send image to worker
onImageLoad = async () => {
  const image = await createImageBitmap(img)
  console.log('posting image', image)
  worker.postMessage({ type: "image", image }, [image])
}
onImageError = e => {
  console.error(e)
}

// create img file
let img = new Image()
img.onload = onImageLoad
img.onerror = onImageError

handleImageUpload = (files) => {
  img.src = URL.createObjectURL(files[0])
}

initWorkerLoad = () => {
  worker.postMessage({ start: true })
}

// send commands to worker
handleBrightness = () => worker.postMessage({ type: "brightness", value: brightnessSetting })

handleBlur = () => worker.postMessage({ type: "blur" })

let brightnessSetting = 0
handleBrightnessChange = (val) => {
  console.log(val)
 brightnessSetting = val
 document.getElementById("setting").innerHTML = brightnessSetting
}
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.