You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Canvas高效绘制时fillRect出现X坐标抖动问题排查

实时无线电瀑布频谱图X坐标偏移问题排查

我正在开发一款接收实时无线电数据的瀑布式频谱图,它会随数据接收垂直滚动。为优化彩色图形的绘制效率,我尝试改用按颜色收集坐标位置的方法,以此减少Canvas fillStyle 的切换次数。该方法效率显著提升,但绘图结果出现了奇怪的X坐标随机轻微偏移问题。

两种绘制方法的简化示例

方法1 - 效率较低的原始实现

// Method 1 - less efficient

let paintWaterfallRow = (
  ctx: CanvasRenderingContext2D, data: FFTSample[], lineWidth: number, lineHeight: number, 
  z: d3.ScaleSequential<string>, dy?: number
) => {
  let chunkWidth = Math.ceil(lineWidth / data.length) + 1;
  dy = dy || 0;
  for (let i = 0; i < data.length; i++) {
    let point = data[i];
    let color = z(point.magnitude);
    ctx.fillStyle = color;
    ctx.fillRect((i / data.length) * lineWidth, dy, chunkWidth, lineHeight);
  }
}

for (const sample of data) {

  ...

  if ((sample.time - lastTime) > TIME_BETWEEN_LINES) {  // sync with desired number of frames
    let dy = y(sample.time);
    let offset = dy - (lastDy + LINE_HEIGHT);  // compensate for jitters in the time math calculation

    ...

    paintWaterfallRow(ctx, sample.payload, width, (LINE_HEIGHT + 0.5) + offset, zScale.scale, (dy - LINE_HEIGHT) - offset);

    lastTime = sample.time;
    lastDy = dy;
  }
}

方法2 - 优化后但出现偏移的实现

// Method 2 - more efficient

let posData: {
  [_: string]: {
    x: number,
    y: number,
    height: number
  }[]
} = {};

for (const sample of data) {
  
  ...

  if ((sample.time - lastTime) > TIME_BETWEEN_LINES) {  // sync with desired number of frames
    let dy = y(sample.time);
    let offset = dy - (lastDy + LINE_HEIGHT);  // compensate for jitters in the time math calculation

    ...

    // collect position data

    let payload = sample.payload;
    for (let i = 0; i < payload.length; i++) {
      let samplePoint = payload[i];
      let color = zScale.scale(samplePoint.magnitude);

      if (!posData.hasOwnProperty(color))
        posData[color] = [];

      posData[color].push({
        x: (i / payload.length) * width,
        y: (dy - LINE_HEIGHT) - offset,
        height: (LINE_HEIGHT + 0.5) + offset
      })
    }

    lastTime = sample.time;
    lastDy = dy;
  }
}

// draw collected points

const chunkWidth = Math.ceil(width / sample.payload.length) + 1;

for (const [color, points] of Object.entries(posData)) {
  ctx.fillStyle = color;
  for (let i = 0; i < points.length; i++) {
    let point = points[i];
    ctx.fillRect(point.x, point.y, chunkWidth, point.height);
  }
}

绘制效果对比

方法1绘制结果(线条清晰笔直)

方法1绘制结果

方法2绘制结果(X坐标随机轻微偏移)

方法2绘制结果

已排查信息

  • 每条样本数据的长度始终为64
  • 将方法2中所有X坐标存入集合后,集合长度保持64,说明X坐标计算无偏差

请问问题出在哪里?


内容的提问来源于stack exchange,提问作者moltarze

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 12:22:33