Search code examples
javascriptweb-workerwebgl2offscreen-canvas

How to move image processing code entirely to web-worker


I want to transfer full control to worker and use off screen canvas. But there is an Image() which is tied to UI, see function makeImg. I am not intending to show the image, it has pure data usage for building a mesh. The highlighted code fully depends on UI. Is it possible (and how exactly) to do it entirely in web worker, without interchanging data with UI until calculations done and the final mesh fully generated, ready to be shown? For instance following bitmap contains the heights:
enter image description here
The page without perspective with full source codes is here.
I am building height terrain using above bitmap as heightmap, code is here on GitHub, in the page heightMap.html. So, I use pixel values being used to generate vertices, calculate normals, texture coordinate. The result is the terrain going to be shown in the page, here shown without texture:
enter image description here

async function readImgHeightMap (src, crossOrigin) {
   return new Promise ((resolve, reject) => {
      readImg (src, crossOrigin).then ((imgData) => {
         let heightmap = [];
         //j, row -- z coordinate; i, column -- x coordinate
         //imgData.data, height -- y coordinate
         for (let j = 0, j0 = 0; j < imgData.height; j++, j0 += imgData.width * 4) {
            heightmap[j] = [];
            for (let i = 0, i0 = 0; i < imgData.width; i++, i0 += 4)
               heightmap[j][i] = imgData.data[j0 + i0];
         }
         resolve( {data:heightmap, height:imgData.height, width:imgData.width} );
      });
   });
}

async function readImg (src, crossOrigin) {
   return new Promise  ( (resolve, reject) => {
      makeOffscreenFromImg (src, crossOrigin).then((canvas) => {
         let ctx     = canvas.getContext("2d");
         let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height, { colorSpace: "srgb" });
         resolve(imgData);
      });
   });
}

async function makeOffscreenFromImg (src, crossOrigin) {
   let img  = makeImg(src, crossOrigin);
   return new Promise((resolve, reject) => {
      img.addEventListener('load',  () => {
         let cnv = new OffscreenCanvas(img.width, img.height);
         cnv.getContext("2d").drawImage(img, 0, 0);
         resolve(cnv);
      });
      img.addEventListener('error', (event) => { console.log(event); reject (event); } );
   });
}
function makeImg (src, crossOrigin)
{
   let image  = new Image ();
   let canvas = document.createElement("canvas");
   if (crossOrigin) image.crossOrigin  =  crossOrigin;
   image.src  =  src;
   return image;
}

##################
PS: Just in case, to see the crater from different angles, camera can be moved with mouse when pressing SHIFT, or rotated when pressing CTRL. Also click event for permanent animation, or tap if on mobile device.
PS1: Please do not use heightmap images for personal purposes. These have commercial copyright.


Solution

  • Use createImageBitmap from your Worker, passing a Blob you'd have fetched from the image URL:

    const resp = await fetch(imageURL);
    if (!resp.ok) {
      throw "network error";
    }
    const blob = await resp.blob();
    const bmp = await createImageBitmap(blob);
    const { width, height } = bmp;
    const canvas = new OffscreenCanvas(width, height);
    const ctx = canvas.getContext("2d");
    ctx.drawImage(bmp, 0, 0);
    bmp.close();
    const imgData = ctx.getImageData(0, 0, width, height);
    

    If required, you could also create the ImageBitmap from the <img> tag in the main thread, and transfer it to your Worker.