Ser*_*sky 1 html javascript css canvas
我正在尝试制作一个带有 2 个画布的像素编辑器。第一个画布显示包含像素的第二个画布。第一个画布使用drawImage来定位和缩放第二个画布。
当第二个画布缩放得小于其原始尺寸时,它开始出现故障。
这是以原始尺寸显示的画布。当我放大时,第二个画布变得更大,一切都完美运行。
然而,当我缩小时,网格和背景(透明度)的表现非常奇怪。
要在第一个画布上绘制第二个画布,我使用该函数
ctx.drawImage(drawCanvas, offset.x, offset.y, width * pixelSize, height * pixelSize);
Run Code Online (Sandbox Code Playgroud)
我读过多次迭代中的缩放可能会提供更好的图像质量,但我不确定画布。
当用户缩小时,我可以以较低的分辨率完全重绘第二个画布,但这对 CPU 来说有点沉重。
还有我不知道的更好的解决方案吗?
您的问题来自抗锯齿。
像素是不可再分的,当您要求计算机在像素边界之外绘制某些内容时,它会尽力通过混合颜色来呈现通常看起来不错的内容,以便本来应该是黑色的内容例如,0.1 像素线将变成浅灰色像素。
这通常效果很好,特别是对于真实文字的图片或圆形等复杂形状。然而对于网格...这并不像您所经历的那么好。
您的案例正在处理两个不同的案例,您必须分别处理折边。
在画布 2D API(以及许多 2D API)中,stroke确实会从您设置的坐标的两侧渗色。因此,当绘制 1 像素宽的线条时,您需要考虑 0.5 像素的偏移量,以确保它不会渲染为两个灰色像素。有关这方面的更多信息,请参阅此答案。您可能正在对网格使用这样的笔画。
fill另一方面,仅覆盖形状的内部,因此如果填充矩形,则不需要将其坐标从 px 边界偏移。这是棋盘格所必需的。
现在,对于这些绘图,最好的可能是使用图案。你只需要画出它的一个小版本,然后图案就会自动重复,节省大量的计算量。
图案的缩放可以通过调用 2D 上下文的变换方法来完成。我们甚至可以通过将imageSmoothingEnabled属性设置为 false,利用最近邻算法来避免在绘制此图案时出现抗锯齿。
然而对于我们的网格,我们可能希望保持线宽不变。为此,我们需要在每次绘制调用时生成一个新模式。
// An helper function to create CanvasPatterns
// returns a 2DContext on which a simple `finalize` method is attached
// method which does return a CanvasPattern from the underlying canvas
function patternMaker(width, height) {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.finalize = (repetition = "repeat") => ctx.createPattern(canvas, repetition);
return ctx;
}
// The checkerboard can be generated only once
const checkerboard_patt_maker = patternMaker(2, 2);
checkerboard_patt_maker.fillStyle = "#CCC";
checkerboard_patt_maker.fillRect(0,0,1,1);
checkerboard_patt_maker.fillRect(1,1,1,1);
const checkerboard_patt = checkerboard_patt_maker.finalize();
// An helper function to create grid patterns
// Since we want a constant lineWidth, no matter the zoom level
function makeGridPattern(width, height) {
width = Math.round(width);
height = Math.round(height);
const grid_patt_maker = patternMaker(width, height);
grid_patt_maker.lineWidth = 1;
// apply the 0.5 offset only if we are on integer coords
// for instance a <3,3> pattern wouldn't need any offset, 1.5 is already perfect
const x = width/2 % 1 ? width/2 : width/2 + 0.5;
const y = height/2 % 1 ? height/2 : height/2 + 0.5;
grid_patt_maker.moveTo(x, 0);
grid_patt_maker.lineTo(x, height);
grid_patt_maker.moveTo(0, y);
grid_patt_maker.lineTo(width, y);
grid_patt_maker.stroke();
return grid_patt_maker.finalize();
}
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const checkerboard_input = document.getElementById('checkerboard_input');
const grid_input = document.getElementById('grid_input');
const connector = document.getElementById('connector');
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const checkerboard_zoom = checkerboard_input.value;
const grid_zoom = grid_input.value;
// we generate a new pattern for the grid, so the lineWidth is always 1
const grid_patt = makeGridPattern(grid_zoom, grid_zoom);
// draw once the rectangle covering the whole canvas
// with normal transforms
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
// the checkerboard
ctx.fillStyle = checkerboard_patt;
// our path is already drawn, we can control only the fill
ctx.scale(checkerboard_zoom, checkerboard_zoom);
// avoid antialiasing when painting our pattern (similar to rounding the zoom level)
ctx.imageSmoothingEnabled = false;
ctx.fill();
// done, reset to normal
ctx.imageSmoothingEnabled = true;
ctx.setTransform(1, 0, 0, 1, 0, 0);
// paint the grid
ctx.fillStyle = grid_patt;
// because our grid is drawn in the middle of the pattern
ctx.translate(Math.round(grid_zoom/2), Math.round(grid_zoom/2));
ctx.fill();
// reset
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
draw();
checkerboard_input.oninput = grid_input.oninput = function(e) {
if(connector.checked) {
checkerboard_input.value = grid_input.value = this.value;
}
draw();
};
connector.oninput = e => checkerboard_input.oninput();Run Code Online (Sandbox Code Playgroud)
<label>checkerboard-layer zoom<input id="checkerboard_input" type="range" min="2" max="50" step="0.1"></label><br>
<label>grid-layer zoom<input id="grid_input" type="range" min="2" max="50" step="1"></label><br>
<label>connect both zooms<input id="connector" type="checkbox"></label>
<canvas id="canvas"></canvas>Run Code Online (Sandbox Code Playgroud)