【问题标题】:Firefox, Edge, Safari canvas drawing with putImageData failsFirefox、Edge、Safari 使用 putImageData 绘制画布失败
【发布时间】:2018-08-07 10:11:00
【问题描述】:

jsfiddle:https://jsfiddle.net/nragrkb8/3/

我正在尝试绘制频谱图,可以在 chrome 中使用,但在所有其他浏览器中都没有绘制任何内容。每次推入新数据时,我都使用 getImageData 和 putImageData 将现有内容向下移动一个像素。然后将新数据绘制到屏幕外图像中,然后 putimagedata 将其应用于整个画布。

Spectrogram = (function() {

  //constructor function
  var Spectrogram = function(options) {
    //compose options
    this.canvas = options.canvas;
    this.width = options.size; //the size of the FFT
    this.height = options.length; //the number of FFT to keep

    if (typeof options.min !== 'undefined' && typeof options.max !== 'undefined') {
      this.rangeType = 'static';
      this.min = options.min;
      this.max = options.max;
    } else {
      this.rangeType = 'auto';
      this.min = Infinity;
      this.max = -Infinity;
    }

    //set the function used to determine a value's color
    this.colorScaleFn = options.colorScaleFn || defaultColorScaling;

    //precalculate the range used in color scaling
    this.range = this.max - this.min;

    //the 2d drawing context
    this.ctx = this.canvas.getContext('2d', { alpha: false });

    //single pixel image used to draw new data points
    this.im = this.ctx.createImageData(this.width, 1);

    this.canvas.width = this.width;
    this.canvas.height = this.height;

    //adds a new row of data to the edge of the spectrogram, shifting existing
    //contents by 1px
    this.addData = function (newData) {
      if (newData.length != this.width) {
        console.error('Unmatched dataset size. Expected ' + this.width + ', was + ' + newData.length);
        return;
      }

      if (this.rangeType == 'auto') {
        var changed = false;

        for (var i = 0; i < newData.length; ++i) {
          if (newData[i] < this.min) {
            this.min = newData[i];
            changed = true;
          } else if (newData[i] > this.max) {
            this.max = newData[i];
            changed = true;
          }
        }

        if (changed) {
          this.range = this.max - this.min;
        }
      }

      //move the current contents by 1px
      var im = this.ctx.getImageData(0, 0, this.width, this.height - 1);
      this.ctx.putImageData(im, 0, 1);

      //draw the new data values into the temporary image
      var j = 0;
      for (var i = 0; i < newData.length; ++i) {
        var c = this.colorScaleFn(newData[i]);
        this.im.data[j] = c[0];
        this.im.data[j + 1] = c[1];
        this.im.data[j + 2] = c[2];
        j += 4;
      }

      //put the new data colors into the full canvas
      this.ctx.putImageData(this.im, 0, 0);
    }
  }

  function defaultColorScaling(value) {
    var x = (value - this.min) / this.range;
    if (x < 0) {
      x = 0;
    } else if (x > 1) {
      x = 1;
    }

    var g = Math.pow(x, 2);
    var b = 0.75 - 1.5 * Math.abs(x - 0.45);
    var r = 0;

    return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
  }

  return Spectrogram;
})();

const size = 1024;
      const length = 200;

      const dataMax = 100;
      const velMax = 1;

      const canvas = document.getElementById('spec');
      canvas.width = size;
      canvas.height = length;

      const spec = new Spectrogram({
        canvas: canvas,
        size: size,
        length: length
      });

      const data = [];
      const vel = [];
      for (var i = 0; i < size; ++i) {
        data.push(0);
        vel.push(0);
      }

      setInterval(function() {
        for (var i = 0; i < size; ++i) {
          data[i] += vel[i];

          if (i > 0) data[i] += vel[i - 1];
          if (i < size - 1) data[i] += vel[i + 1];

          if (data[i] >= dataMax) {
            data[i] = dataMax;
            vel[i] = 0;
          } else if (data[i] <= -dataMax) {
            data[i] = -dataMax;
            vel[i] = 0;
          }

          if (vel[i] == 0) {
            vel[i] = (Math.random() - 0.5) * 100;
          }
        }

        spec.addData(data);
      }, 100);

【问题讨论】:

  • 我在 edge 或 firefox 的控制台中没有收到任何错误。
  • 如果你想画一些东西,你需要将alpha通道设置为大于0。 jsfiddle.net/nragrkb8/10 另外,请注意,您应该不惜一切代价避免高频 setInterval 执行可能需要超过间隔时间的任务(在您的情况下可以),而是使用类似 requestAnimtionFrame 循环的东西。不要使用 getImageData + putImageData 来更新您的上下文,只需使用 ctx.drawImage(ctx.canvas, x, y)。您的代码中可能还有其他问题需要修复,但这里有一个 updated fiddle 包含这两个主要内容。
  • 看来所有其他浏览器都忽略了 alpha: false 选项?
  • 另外,您应该将其发布为答案,以便我接受!
  • alpha: false 不应该关注 createImageData,这是 chrome 中的一个错误。我把这个问题更多地看作是一个typo,所以VTCed而不是回答它。但如果你不同意,我可以写一个。

标签: javascript canvas


【解决方案1】:

您的问题是由误解引起的:

getContext('2d', {alpha: false}) 应该与 createImageData 函数无关,因此您仍然必须将 ImageData 的 alpha 值设置为 0 以外的值。

您的代码中还有其他一些不顺利的地方,即使您当前的问题没有解决:

  • 您应该不惜一切代价避免使用高频率 setInterval,在这种情况下您执行的任务可能会花费超过时间间隔(在您的情况下可以),而是使用类似 requestAnimationFrame 循环的东西。
  • 不要使用 getImageData + putImageData 在自身上绘制上下文,只需使用 ctx.drawImage(ctx.canvas, x, y)

您的代码中可能还有其他问题需要修复,但这里有以下三个主要内容的更新:

Spectrogram = (function() {

  //constructor function
  var Spectrogram = function(options) {
    //compose options
    this.canvas = options.canvas;
    this.width = options.size; //the size of the FFT
    this.height = options.length; //the number of FFT to keep

    if (typeof options.min !== 'undefined' && typeof options.max !== 'undefined') {
      this.rangeType = 'static';
      this.min = options.min;
      this.max = options.max;
    } else {
      this.rangeType = 'auto';
      this.min = Infinity;
      this.max = -Infinity;
    }

    //set the function used to determine a value's color
    this.colorScaleFn = options.colorScaleFn || defaultColorScaling;

    //precalculate the range used in color scaling
    this.range = this.max - this.min;

    //the 2d drawing context
    this.ctx = this.canvas.getContext('2d', {
      alpha: false
    });

    //single pixel image used to draw new data points
    this.im = this.ctx.createImageData(this.width, 1);

    this.canvas.width = this.width;
    this.canvas.height = this.height;

    //adds a new row of data to the edge of the spectrogram, shifting existing
    //contents by 1px
    this.addData = function(newData) {
      if (newData.length != this.width) {
        console.error('Unmatched dataset size. Expected ' + this.width + ', was + ' + newData.length);
        return;
      }

      if (this.rangeType == 'auto') {
        var changed = false;

        for (var i = 0; i < newData.length; ++i) {
          if (newData[i] < this.min) {
            this.min = newData[i];
            changed = true;
          } else if (newData[i] > this.max) {
            this.max = newData[i];
            changed = true;
          }
        }

        if (changed) {
          this.range = this.max - this.min;
        }
      }

      //move the current contents by 1px
      this.ctx.drawImage(this.ctx.canvas, 0, 1)

      //draw the new data values into the temporary image
      var j = 0;
      for (var i = 0; i < newData.length; ++i) {
        var c = this.colorScaleFn(newData[i]);
        this.im.data[j] = c[0];
        this.im.data[j + 1] = c[1];
        this.im.data[j + 2] = c[2];
        // don't forget the alpha channel, createImageData is always full of zeroes
        this.im.data[j + 3] = 255;
        j += 4;
      }

      this.ctx.putImageData(this.im, 0, 0);
    }
  }

  function defaultColorScaling(value) {
    var x = (value - this.min) / this.range;
    if (x < 0) {
      x = 0;
    } else if (x > 1) {
      x = 1;
    }

    var g = Math.pow(x, 2);
    var b = 0.75 - 1.5 * Math.abs(x - 0.45);
    var r = 0;

    return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
  }

  return Spectrogram;
})();

const size = 1024;
const length = 200;

const dataMax = 100;
const velMax = 1;

const canvas = document.getElementById('spec');
canvas.width = size;
canvas.height = length;

const spec = new Spectrogram({
  canvas: canvas,
  size: size,
  length: length
});

const data = [];
const vel = [];
for (var i = 0; i < size; ++i) {
  data.push(0);
  vel.push(0);
}
// our animation loop
function draw() {
  for (var i = 0; i < size; ++i) {
    data[i] += vel[i];

    if (i > 0) data[i] += vel[i - 1];
    if (i < size - 1) data[i] += vel[i + 1];

    if (data[i] >= dataMax) {
      data[i] = dataMax;
      vel[i] = 0;
    } else if (data[i] <= -dataMax) {
      data[i] = -dataMax;
      vel[i] = 0;
    }

    if (vel[i] == 0) {
      vel[i] = (Math.random() - 0.5) * 100;
    }
  }

  spec.addData(data);
  requestAnimationFrame(draw);
}
draw();
<div style="width: 100%; height: 100%; padding: 0; margin: 0;">
  <canvas style="width: 100%; height: 100%;" id="spec"></canvas>
</div>

【讨论】:

  • 接受这个作为答案。问题是我在获取上下文时对 alpha:false 参数的误解;我仍然需要为我正在更新的数据行设置一个 alpha 值。谢谢!
  • @TheBurrito 什么版本的chrome正在体验呢?因为经过测试,osX 上的 chrome 64 或 66 似乎都没有受到此类 bug 的影响。
  • 版本 64.0.3282.186(正式版)(64 位)Windows 10
猜你喜欢
  • 1970-01-01
  • 2016-04-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-29
  • 1970-01-01
  • 2012-10-13
相关资源
最近更新 更多