javascript

Adding images pixel by pixel in javascript


I'm trying to write a function that would take images uploaded by user (values argument is an array of Files) and add them up pixel by pixel. The page freezes. Is this function ok?

function addImages(values) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const loadPromises = [];
values.forEach((value) => {
    const image = new Image();
    image.src = URL.createObjectURL(value);
    const loadPromise = new Promise((resolve, reject) => {
        image.onload = () => {
            URL.revokeObjectURL(image.src);
            resolve(image);
        }
        image.onerror = () => {
            URL.revokeObjectURL(image.src);
            reject(new Error('Image loading failed.'));
        }
    });
    loadPromises.push(loadPromise);
})
Promise.all(loadPromises).then(images => {
    const widths = images.map(i => i.width);
    const heights = images.map(i => i.height);
    canvas.width = Math.max(...widths);
    canvas.height = Math.max(...heights);
    const resultData = ctx.createImageData(canvas.width, canvas.height);
    for (let i = 0; i < resultData.data.length; i += 4) {
        images.forEach(image => {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.drawImage(image, 0, 0);
            const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
            resultData.data[i] += imageData.data[i];
            resultData.data[i + 1] += imageData.data[i + 1];
            resultData.data[i + 2] += imageData.data[i + 2];
        })
    }
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.putImageData(resultData, 0, 0);
    return canvas.toDataURL();
});}

Solution

  • You may be able to achieve the same result by using ctx.globalCompositeOperation = "lighter"; then simply adding the images to the canvas one by one

    This results in a speedup of, well, it's hard to calculate almost instant vs multiple minutes for even image sizes of less than 100x100 pixels - let's just call the improvement in speed significant

    Note: the only thing I'm not sure about is what happens in your code when values "overflow" (i.e. > 255) in your code - but, I haven't the patience to run your code :p

    function addImages(values) {
      const canvas = document.createElement('canvas');
      const ctx = canvas.getContext('2d', {
        willReadFrequently: true
      });
      const loadPromises = [];
      values.forEach((value) => {
        const image = new Image();
        image.src = URL.createObjectURL(value);
        const loadPromise = new Promise((resolve, reject) => {
          image.onload = () => {
            URL.revokeObjectURL(image.src);
            resolve(image);
          }
          image.onerror = () => {
            URL.revokeObjectURL(image.src);
            reject(new Error('Image loading failed.'));
          }
        });
        loadPromises.push(loadPromise);
      })
      return Promise.all(loadPromises)
        .then(images => {
          const widths = images.map(i => i.width);
          const heights = images.map(i => i.height);
          canvas.width = Math.max(...widths);
          canvas.height = Math.max(...heights);
          const resultData = ctx.createImageData(canvas.width, canvas.height);
          ctx.clearRect(0, 0, canvas.width, canvas.height);
          ctx.globalCompositeOperation = "lighter";
          images.forEach(image => ctx.drawImage(image, 0, 0));
          return canvas.toDataURL();
        });
    }
    const input = document.querySelector('#files');
    const image = document.querySelector('#target');
    input.addEventListener('change', async() => {
      const url = await addImages([...input.files]);
      image.src = url;
    });
    <div>
      <input id="files" type="file" multiple/>
    </div>
    
    <img id="target" />