kleisauke / wasm-vips

libvips for the browser and Node.js, compiled to WebAssembly with Emscripten.
https://kleisauke.github.io/wasm-vips/
MIT License
463 stars 25 forks source link

```Uncaught RuntimeError: Aborted(OOM)``` when writeToBuffer #34

Closed hurryhuang1007 closed 1 year ago

hurryhuang1007 commented 1 year ago

Hello, I want to use this library to complete the following tasks: combine multiple small images into one large image in the browser and download it to the user's local. So I write a demo, here are some main code snippets:

const urls = [have 1000 urls]
console.log('urls.length', urls.length)

const WIDTH = 1000
const HEIGHT = 720

const colNum = ~~Math.sqrt(urls.length)
const rowNum = ~~(urls.length / colNum)

window.startMerge = async () => {
  const images = await Promise.all(
    urls.map(async (url, index) => {
      return vips.Image.newFromBuffer(await fetch(url).then(res => res.arrayBuffer()))
    })
  )
  // merge images
  let mergeImage = await vips.Image.black(WIDTH * colNum, HEIGHT * rowNum)
  for (let i = 0; i < rowNum; i++) {
    for (let j = 0; j < colNum; j++) {
      const image = images[i * colNum + j]
      mergeImage = await mergeImage.insert(image, j * WIDTH, i * HEIGHT)
      // dispose image
      image.delete()
    }
  }
  console.log('mergeImage', mergeImage)

  const buffer = await mergeImage.writeToBuffer('.jpg') // OOM happened
  // const blob = new Blob([buffer], { type: 'image/jpeg' })
  // const url = URL.createObjectURL(blob)
  // const img = document.createElement('img')
  // img.src = url
  // img.style.position = 'fixed'
  // img.style.top = '0'
  // img.style.left = '0'
  // img.style.zIndex = '9999'
  // document.body.appendChild(img)
}

Does this mean that large files need to be streamed out? Instead of the overall output as it is now.

kleisauke commented 1 year ago

The issue with image.insert() is that libvips has to recurse very deeply when it evaluates the pipeline containing a large amount of images, causing an OOM error or a stack overflow.

If your images are a regular grid, you might consider using vips.Image.arrayjoin() instead, which would be faster and use much less memory. For example:

const urls = [...];

const images = await Promise.all(
  urls.map(async url => 
    vips.Image.newFromBuffer(await fetch(url).then(res => res.arrayBuffer()), '', {
      access: vips.Access.sequential // 'sequential'
    })
  )
);

const im = vips.Image.arrayjoin(images, {
  across: Math.floor(Math.sqrt(urls.length)), // number of images per row
  shim: 10, // space between images, in pixels
  background: [0, 0, 0] // background colour
});

const buffer = im.writeToBuffer('.jpg');
kleisauke commented 1 year ago

I hope this information helped. Please feel free to re-open if questions remain.