Search code examples
javascriptreactjsasync-awaithtml5-canvasreact-dropzone

Scale Images using Canvas and wait for Result


I am using react-dropzone to upload files.

<Dropzone
  accept="image/jpeg"
  multiple
  onDrop={(acceptedFiles, rejectedFiles) => this.onDrop(acceptedFiles, rejectedFiles)}
/>

In the onDrop function, I scale each image that I get using canvas.

onDrop(acceptedFiles: Array<Object>, rejectedFiles: Array<Object>) {

  if (acceptedFiles.length) {
    const requestParams = {
      parameters: {
        generateContourImages: false,
      },
      images: [],
    };
    acceptedFiles.forEach(async (acceptedFile) => {
      const { width, height } = await browserImageSize(acceptedFile);
      const imagedata = await this.scaleImage(acceptedFile, width, height);
      requestParams.images.push({
        imagedata,
        methods: methods,
        imageID: uuidv4(),
      });
    });
  } else {
    this.setState({ error: true });
  }
}

The actual scaling happens in the function below

async scaleImage(fileImage: Object, originalWidth: number, originalHeight: number) {

  return new Promise((resolve) => {

    const imageURL = URL.createObjectURL(fileImage);

    const image = new Image();

    image.onload = () => {
      const scaleFactor = Math.max(originalWidth, originalHeight) / 1000;

      const canvas = document.createElement('canvas');
      canvas.width = originalWidth;
      canvas.height = originalHeight;

      const scaledWidth = Math.floor(originalWidth / scaleFactor);
      const scaledHeight = Math.floor(originalHeight / scaleFactor);

      const ctx = canvas.getContext('2d');
      ctx.drawImage(image, 0, 0, scaledWidth, scaledHeight);

      const base64 = canvas.toDataURL('image/jpeg');
      resolve(base64.substr(23));
    }

    image.src = imageURL;

  });
}

The requestParams object that I construct will then be send to the server. My problem is that I want my client-side code to wait till it goes through all the images and constructs the final requestParams object. That's not the case now. How do I make the above piece of code wait?


Solution

  • It can be achieved in the following manner:

    async constructRequestParams(acceptedFiles: Array<Object>) {
      return new Promise((resolve) => {
        const requestParams = {
          parameters: {
            generateContourImages: false,
          },
          images: [],
        };
        acceptedFiles.forEach(async (acceptedFile, index) => {
          const { width, height } = await browserImageSize(acceptedFile);
          const imagedata = await this.scaleImage(acceptedFile, width, height);
          requestParams.images.push({
            imagedata,
            methods: methods,
            imageID: uuidv4(),
          });
          if (requestParams.images.length === acceptedFiles.length) {
            resolve(requestParams);
          }
        });
      })
    }
    
    async onDrop(acceptedFiles: Array<Object>, rejectedFiles: Array<Object>) {
      if (acceptedFiles.length) {
        const requestParams = await this.constructRequestParams(acceptedFiles);
      } else {
        this.setState({ error: true });
      }
    }