从画布复制到画布时图像模糊

时间:2018-09-12 07:16:06

标签: javascript html 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';

1 个答案:

答案 0 :(得分:1)

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

但是请注意,您的闪烁问题可能是由于resize事件的触发频率可能高于屏幕刷新率。
因此,您最终要重置整个上下文+全部重绘+在单个帧中多次缩放图像。

因此,您很有可能确实会发生闪烁。

为避免这种情况,请使用 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回调的执行后,最后一个动作将是我们的调整大小处理程序,并将绘制一个空白画布。

因此,对于动画内容,您需要直接从动画循环中处理这种情况。
您将调整大小处理程序设置为仅引发一个标志,让主循环知道在执行任何其他操作之前应调整画布的大小。 如果自上一帧以来没有发生任何大小调整事件,请忽略此更新,然后继续动画循环的其余部分。

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

// 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>