photopea / UTIF.js

Fast and advanced TIFF decoder
MIT License
426 stars 87 forks source link

How to handle data down sampling after `RangeError: Array buffer allocation failed` #125

Open usagibear opened 8 months ago

usagibear commented 8 months ago

I ran into a range error due to my image being very large (50k x 30k pixels with CCITT T.6 compression). I was trying to down sample the 1D data array idfs[0].data I get from UTIF.decodeImage(e.target.result, ifds[0]), creating a new object and passing it to let rgba = UTIF.toRGBA8(). rgba is garbled when I use ctx.putImage(imageData, 0, 0). After I inspected some of the data in the idfs[0].data array, it looks like some values are undefined. Does idfs[0].data represent the raw image data? What could be the problem?

The object:

let smallifds = {
  data: downsample(ifds[0].data, width, height, windowWidth, windowHeight), 
  width: ifds[0].width / windowWidth, 
  height: ifds[0].height/ windowHeight
};

where downsample() takes the average of the data in a window/grid of windowWidth * windowHeight.

I tested with a smaller TIFF that works with ctx.putImage() and tried to scale it down by half by using the same method of down sampling the ifds[0].data but the data is garbled.

Thanks for your help!

photopea commented 8 months ago

Since you are using CCITT, the raw image is 1 bit per pixel. So 1 Byte is 8 pixels. The size of idfs[0].data should be (width x height) / 8 Bytes. Are you sure your downsample method can process this raw image?

usagibear commented 8 months ago

For the small image I have, its resolution is 1771 * 1220 (width height). By the formula you mentioned, it should be `270077.5 = (1771 1220) / 8. However, when I doifds[0].data.length, I get270840` instead.

The way I am down sampling may also be the problem:


// Get the average sum of a window of values
const avgArr = (arr, wWidth, wHeight) => {
  let sum = 0;
  let count = 0;
  for (let y = 0; y < wHeight; y++) {
    for (let x = 0; x < windowWidth; x++) {
      count++;
      sum += arr[y * wWidth + x];
    }
  }
  return sum / count;
};

// wWidth: window width
// wHeight: window height
const downSample = (src, width, height, wWidth, wHeight) => {
  let dst = [];
  let hBlk = height / wHeight; // height block
  let wBlk = width / wWidth; // width block
  let blk = wHeight * wWidth; // block

  for (let zh = 0; zh < hBlk; zh++) {
    for (let zw = 0; zw < wBlk; zw++) {
      let subset = [];
      for (let y = 0; y < wHeight; y++) {
        for(let x = 0; x < wWidth; x++) {
          subset = subset.concat([src[zh * wBlk * blk + zw * wWidth + y * width + wWidth * x]]);
        }
      let avgValueResult = avgValue(subset, wWidth, wHeight);
      dst = dst.concat([avgValueResult]);
    }
  }

  return dst;
};
photopea commented 8 months ago

I think the number of bytes per line of pixels is rounded to a whole number of bytes.

Each line contains 1771 pixels, which is 221.375 Bytes, but it is rounded to 222 Bytes. So you should have 222 x 1220 = 270840 Bytes.