我有一个带有图像和线条的 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/