admin管理员组

文章数量:1124163

I have a code that should affect an image by applying a laplace filter to the top left quarter and to the bottom left quarter separately. But when doing so, sometimes the second application of the filter affects the bottom right instead and sometimes misses a channel. I cannot for the life of me understand why it is apparently nondeterministic when doing so.

var canvas;
var canvasImageData;
var canvasContext;
main()

let kernel = [
  [0.25, 0.5, 0.25],
  [0.5, -3, 0.5],
  [0.25, 0.5, 0.25]
]

async function main() {
  var api = await fetch('/api/breeds/image/random')
    .then(response => response.json())

  var image = await fetch(api.message)
    .then(response => response.blob())

  const imageBitmap = await createImageBitmap(image);

  var canvas = document.createElement('canvas');
  canvas.width = imageBitmap.width;
  canvas.height = imageBitmap.height;
  canvas.id = 'image-canvas';

  canvasContext = canvas.getContext('2d')
  canvasContext.drawImage(imageBitmap, 0, 0);

  document.body.appendChild(canvas);

  canvasImageData = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height);
  canvasImageData = applyLaplace(canvasImageData, 0, canvas.height / 2, 0, canvas.width / 2, canvas.width, canvas.height, kernel);
  canvasImageData = applyLaplace(canvasImageData, canvas.height / 2, canvas.height, 0, canvas.width / 2, canvas.width, canvas.height, kernel);

  canvasContext.putImageData(canvasImageData, 0, 0);
}

//applyLaplace(canvasImageData)
function applyLaplace(canvas, height_start, height_end, width_start, width_end, canvasWidth, canvasHeight, kernel) {
  let data = new Uint8ClampedArray(canvas.data);

  function pos(h, l) {
    return h * canvasWidth * 4 + l * 4;
  }

  for (let height = height_start + 1; height < height_end - 1; height++) {
    for (let width = width_start + 1; width < width_end - 1; width++) {

      let sum1 = 0;
      let sum2 = 0;
      let sum3 = 0;

      for (let kernel_height = -1; kernel_height <= 1; kernel_height++) {
        for (let kernel_width = -1; kernel_width <= 1; kernel_width++) {
          sum1 = sum1 + data[pos(height + kernel_height, width + kernel_width) + 0] * kernel[kernel_height + 1][kernel_width + 1];
          sum2 = sum2 + data[pos(height + kernel_height, width + kernel_width) + 1] * kernel[kernel_height + 1][kernel_width + 1];
          sum3 = sum3 + data[pos(height + kernel_height, width + kernel_width) + 2] * kernel[kernel_height + 1][kernel_width + 1];
        }
      }

      canvas.data[pos(height, width) + 0] = 255 - sum1;
      canvas.data[pos(height, width) + 1] = 255 - sum2;
      canvas.data[pos(height, width) + 2] = 255 - sum3;
    }

  }
  return canvas;
}
<div id="api-response"></div>

I have a code that should affect an image by applying a laplace filter to the top left quarter and to the bottom left quarter separately. But when doing so, sometimes the second application of the filter affects the bottom right instead and sometimes misses a channel. I cannot for the life of me understand why it is apparently nondeterministic when doing so.

var canvas;
var canvasImageData;
var canvasContext;
main()

let kernel = [
  [0.25, 0.5, 0.25],
  [0.5, -3, 0.5],
  [0.25, 0.5, 0.25]
]

async function main() {
  var api = await fetch('https://dog.ceo/api/breeds/image/random')
    .then(response => response.json())

  var image = await fetch(api.message)
    .then(response => response.blob())

  const imageBitmap = await createImageBitmap(image);

  var canvas = document.createElement('canvas');
  canvas.width = imageBitmap.width;
  canvas.height = imageBitmap.height;
  canvas.id = 'image-canvas';

  canvasContext = canvas.getContext('2d')
  canvasContext.drawImage(imageBitmap, 0, 0);

  document.body.appendChild(canvas);

  canvasImageData = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height);
  canvasImageData = applyLaplace(canvasImageData, 0, canvas.height / 2, 0, canvas.width / 2, canvas.width, canvas.height, kernel);
  canvasImageData = applyLaplace(canvasImageData, canvas.height / 2, canvas.height, 0, canvas.width / 2, canvas.width, canvas.height, kernel);

  canvasContext.putImageData(canvasImageData, 0, 0);
}

//applyLaplace(canvasImageData)
function applyLaplace(canvas, height_start, height_end, width_start, width_end, canvasWidth, canvasHeight, kernel) {
  let data = new Uint8ClampedArray(canvas.data);

  function pos(h, l) {
    return h * canvasWidth * 4 + l * 4;
  }

  for (let height = height_start + 1; height < height_end - 1; height++) {
    for (let width = width_start + 1; width < width_end - 1; width++) {

      let sum1 = 0;
      let sum2 = 0;
      let sum3 = 0;

      for (let kernel_height = -1; kernel_height <= 1; kernel_height++) {
        for (let kernel_width = -1; kernel_width <= 1; kernel_width++) {
          sum1 = sum1 + data[pos(height + kernel_height, width + kernel_width) + 0] * kernel[kernel_height + 1][kernel_width + 1];
          sum2 = sum2 + data[pos(height + kernel_height, width + kernel_width) + 1] * kernel[kernel_height + 1][kernel_width + 1];
          sum3 = sum3 + data[pos(height + kernel_height, width + kernel_width) + 2] * kernel[kernel_height + 1][kernel_width + 1];
        }
      }

      canvas.data[pos(height, width) + 0] = 255 - sum1;
      canvas.data[pos(height, width) + 1] = 255 - sum2;
      canvas.data[pos(height, width) + 2] = 255 - sum3;
    }

  }
  return canvas;
}
<div id="api-response"></div>

and example of incorrect:

and an example of correct image:

Share Improve this question edited yesterday Kaiido 136k14 gold badges254 silver badges317 bronze badges asked yesterday ion gigelion gigel 12 bronze badges 4
  • Time to start debugging: rather than applyLaplace(canvasImageData, 0, canvas.height / 2, 0, canvas.width / 2, canvas.width, canvas.height, kernel);, capture those arguments as variables, and log those, to see what values they have, so you can work your way back to why those are the values they have. – Mike 'Pomax' Kamermans Commented yesterday
  • @Mike'Pomax'Kamermans found the issue by passing canvas.height/3 as a value. Turns out, there is some sort of undefined behavior when passing float values as an index to an array. So the channel was gone due to x.5 + 1 having a different behavior to x.5 + 0 and x.5 + 2. Solution was to floor canvas.height/n – ion gigel Commented yesterday
  • 1 Array indexes have to be integers, but it won't automatically round them. – Barmar Commented yesterday
  • It's not that it's "some sort of undefined behaviour", the behaviour is actually well defined in that it will return undefined because numerical values are treated as indices, and indices can only be integer. – Mike 'Pomax' Kamermans Commented 20 hours ago
Add a comment  | 

1 Answer 1

Reset to default 0

When the source image's height is odd, your y-axis coords will fall on a .5 float value. In this case, your pos function will return an index that's off by half the width of the image (since there are 4 channels per pixel):

const canvasWidth = 10;
function pos(h, l) {
  return h * canvasWidth * 4 + l * 4;
}
const test = (x, y) => {
  console.log({ x, y, index: pos(y, x) });
}
test(0, 0); // 0
test(5, 0); // 4 * 5 -> 20
test(0, 0.5); // Same as x:5, y:0
div.as-console-wrapper { max-height: 100vh }

You thus need to round your coordinates when you do canvas.height / 2.

本文标签: