javascript - 从 Canvas 复制到 Canvas 时图像模糊

标签 javascript html canvas

我有一个带有图像和线条的 Canvas ,我想使其具有响应性。最初我正在重新绘制图像,但它在闪烁,所以我找到了某人的解决方案,将 Canvas 复制到临时 Canvas ,然后再复制回来。但这样做时,图像质量会变得极低且模糊。有什么方法可以获得初始图像质量?

function resize() {         
    var tempCanvas = document.createElement('canvas');
    tempCanvas.width = ctx.canvas.width;
    tempCanvas.height = ctx.canvas.height;
    var tempContext = tempCanvas.getContext("2d");
    tempContext.drawImage(ctx.canvas, 0, 0, tempCanvas.width, tempCanvas.height);       
    canvas.width = tempCanvas.width
    canvas.height = 600 * canvas.width / 1400;  
    ctx.drawImage(tempContext.canvas, 0, 0, canvas.width, canvas.height);
}
window.addEventListener("resize", resize, false);
function drawLines(canvas, context){
    var width = canvas.width;
    var offset = 100 * canvas.height / 600;
    context.beginPath();
    context.moveTo(0, 0);
    context.lineTo(width, 0);
    context.lineTo(width, offset);
    context.fill();

    context.beginPath();
    context.moveTo(0, canvas.height - offset);
    context.lineTo(width, canvas.height);
    context.lineTo(0, canvas.height);
    context.fill();
}
var canvas = document.getElementById("new-canvas");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth; 
canvas.height = 600 * window.innerWidth / 1400;
var img = new Image();
img.onload = () => {
    document.getElementById("canvas").style.height = canvas.height;
    ctx.globalCompositeOperation = 'xor';   
    ctx.drawImage(img, 0, 0, canvas.width, canvas.height); 
    drawLines( canvas, ctx );   
};
img.src = 'image.jpg';

最佳答案

唯一的方法是从原始图像重画。您无法发明已被丢弃的数据。

但请注意,您的闪烁问题可能是由于调整大小事件的触发频率可能高于屏幕刷新率而引起的。
因此,您最终重置了整个上下文+重新绘制了所有内容+在单个帧中多次重新缩放图像。

因此,您很有可能确实遇到了闪烁。

为了避免这种情况,请使用 requestAnimationFrame 限制事件,以便每帧仅处理一次事件。

function throttle(callback) {
  if (typeof callback !== 'function')
    throw new TypeError('A callback function must be passed');
  var active = false; // a simple flag
  var evt; // to keep track of the last event
  function handler() { // fired only when screen has refreshed
    active = false; // release our flag 
    callback(evt);
  }
  return function handleEvent(e) { // the actual event handler
    evt = e; // save our event at each call
    if (!active) { // only if we weren't already doing it
      active = true; // raise the flag
      requestAnimationFrame(handler); // wait for next screen refresh
    }
  };
}

const ctx = canvas.getContext('2d');
const img = new Image();
img.src = 'https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg';
img.onload = start;

const grad = ctx.createRadialGradient(50, 50, 0, 50, 50, 50);
grad.addColorStop(0.2, 'gold');
grad.addColorStop(1, 'transparent');
const bokeh = [];
for(let i=0; i<30; i++) {
  bokeh.push({
    x: Math.random(),
    y: Math.random(),
    s: Math.random()
  });
}



function start() {
  // our resize handler will fire only once per frame
  window.onresize = throttle(resizeHandler);
  resizeHandler();
}
function resizeHandler() {
  canvas.width = innerWidth;
  canvas.height = img.height* (innerWidth / img.width);
  draw();
}
function draw() {
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  ctx.fillStyle = grad;
  ctx.globalCompositeOperation = 'lighter';
  bokeh.forEach(function(b) {
    const size = b.s*canvas.width / img.width;
    const x = b.x * canvas.width;
    const y = b.y * canvas.height;
    ctx.setTransform(size, 0, 0, size, x, y);
    ctx.fillRect(0,0,100, 100);
  });
}
body{margin:0}
<canvas id="canvas"></canvas>

但要注意,虽然这种限制对于静态内容效果很好,但如果您正在运行动画循环,它最终实际上会导致更多闪烁。

事实上,由于动画循环应该由 *requestAnimationFrame¡ 驱动,并且自上一帧开始计划下一个刻度,因此您的动画代码将在我们的节流事件处理程序之前运行。
这意味着当浏览器结束所有堆叠的 rAF 回调的执行时,最后一个操作将是我们的调整大小处理程序,它将绘制一个空 Canvas 。

因此,如果是动画内容,您需要直接从动画循环中处理这种情况。
您将调整大小处理程序设置为简单地引发一个标志,让主循环知道它应该在任何其他操作之前调整 Canvas 的大小。 如果自最后一帧以来没有发生调整大小事件,则只需忽略此更新并继续动画循环的其余部分。

这样,您确保仅在需要时运行代码,并且每帧仅运行一次。

// a simple 'size' object
const size = {
  dirty: true,
  update: () => {
    // will get called from the mainLoop, if dirty
    canvas.width = innerWidth;
    canvas.height = img.height * (innerWidth / img.width);
    size.dirty = false;
  }
};
// the resize handler only rises the size.dirty flag
window.onresize = e => size.dirty = true;


// the main anim loop, called every frame
function mainLoop() {
  // only if it did change
  if (size.dirty) {
    // resizing the canvas is the first step
    size.update();
  }
  // now we can update our objects
  flakes.forEach((flake) => flake.update());
  // and finally draw
  draw();
  // we are a loop
  requestAnimationFrame(mainLoop);
}

function draw() {
  ctx.setTransform(1, 0, 0, 1, 0, 0)
  ctx.globalCompositeOperation = 'source-over';
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  ctx.fillStyle = grad;
  ctx.globalCompositeOperation = 'lighter';
  flakes.forEach((flake) => flake.draw());
}

const ctx = canvas.getContext('2d');
const img = new Image();
const grad = ctx.createRadialGradient(50, 50, 0, 50, 50, 50);
grad.addColorStop(0.2, 'gold');
grad.addColorStop(1, 'transparent');

class Flake {
  constructor() {
    this.x = Math.random();
    this.y = Math.random();
    this.s = Math.random();
    this.wind = this.weight = this.s * 0.001;
    this.dx = Math.random() * this.s * 0.1;
  }
  update() {
    let dx = this.dx += this.wind;
    this.x += Math.sin(dx * 0.01);
    if (Math.abs(dx) > .1 * this.s) this.wind *= -1;

    this.y += this.weight;
    if (this.y > 1) this.y = ((this.s * 100) / canvas.height) * -1;
  }
  draw() {
    const size = this.s * canvas.width / img.width;
    const y = this.y * canvas.height;
    const rad = size * 50;
    const area = canvas.width + (rad * 2);
    const x = ((this.x * canvas.width) % area) - (rad * 2);

    ctx.setTransform(size, 0, 0, size, x, y);
    ctx.fillRect(0, 0, 100, 100);
  }
}
const flakes = [];
for (let i = 0; i < 30; i++) {
  flakes.push(new Flake());
}

img.src = 'https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg';
img.onload = mainLoop;
body {
  margin: 0
}
<canvas id="canvas"></canvas>

关于javascript - 从 Canvas 复制到 Canvas 时图像模糊,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52289500/

相关文章:

Javascript RegExp - 从字符串中提取电话号码

javascript - 这是一个无限循环,但我终究无法弄清楚为什么

delphi - 如何在窗体上绘制透明文字?

php - 带重音的单词在mysql中出现奇怪的字符

javascript - 如何在 PHP 上使用输入搜索下拉列表

javascript - JS中仅删除canvas的特定元素

javascript - 向 Javascript Date 对象添加天数,并增加月份

javascript - 在双方或仅在服务器端验证表单?

javascript - 单击链接时如何动态获取用户名或详细信息

html - 在制作这个模型时需要帮助