jac*_*rdy 1 javascript html5 canvas
我有一个图像是一个包含像这样的盒装区域的背景:
我知道那个形状的角落的确切位置,我想在其中放置另一个图像.(所以它似乎在盒子里面).
我知道HTML5画布的drawImage方法,但似乎只支持x,y,width,height参数而不是精确坐标.我如何在一组特定坐标上将图像绘制到画布上,理想情况下,浏览器本身可以处理拉伸图像.
小智 7
解决这个问题的一种方法是使用四边形变换.它们与3D变换不同,并允许您绘制到画布,以防您想要导出结果.
这里显示的例子是简化的,并且在渲染本身上使用基本的子分割和"作弊" - 也就是说,它绘制一个小方块而不是细分单元格的形状,但由于尺寸小而重叠我们在许多非极端情况下可以逃脱它.
正确的方法是将形状分成两个三角形,然后在目标位图中按像素扫描,将点从目标三角形映射到源三角形.如果位置值是小数,则可以使用它来确定像素插值(例如,双线性2x2或双立方4x4).
我不打算在这个答案中涵盖所有这些,因为它很快就会超出SO格式的范围,但是这种方法可能适用于这种情况,除非你需要为它设置动画(如果你不够高效的话)想要高分辨率).
让我们从最初的四边形开始:

第一步是在每个条C1-C4和C2-C3上插入Y位置.我们需要当前的位置以及下一个位置.我们将使用线性插值("lerp")使用标准化值t:
y1current = lerp( C1, C4, y / height)
y2current = lerp( C2, C3, y / height)
y1next = lerp(C1, C4, (y + step) / height)
y2next = lerp(C2, C3, (y + step) / height)
Run Code Online (Sandbox Code Playgroud)
这为我们提供了一条在外垂直条之间和沿着外垂直条的新线.

接下来,我们需要该行的X位置,包括当前和下一个.这将为我们提供四个位置,我们将填充当前像素,无论是原样还是内插(此处未显示):
p1 = lerp(y1current, y2current, x / width)
p2 = lerp(y1current, y2current, (x + step) / width)
p3 = lerp(y1next, y2next, (x + step) / width)
p4 = lerp(y1next, y2next, x / width)
Run Code Online (Sandbox Code Playgroud)
x并将y使用整数值在源图像中的位置.

我们可以在循环中使用此设置,该循环将迭代源位图中的每个像素.
演示可以在答案的底部找到.移动圆形手柄以转换并播放步长值,以查看其对性能和结果的影响.
该演示将具有云纹和其他工件,但如前所述,这将是另一天的主题.
演示快照:

您还可以使用WebGL或Three.js来设置3D环境并渲染到画布.以下是后一解决方案的链接:
以及如何使用纹理映射表面的示例:
使用此方法还可以将结果导出到画布或图像,但是为了提高性能,客户端需要GPU.
如果您不需要导出或操作结果,我建议使用简单的CSS 3D变换,如其他答案所示.
/* Quadrilateral Transform - (c) Ken Nilsen, CC3.0-Attr */
var img = new Image(); img.onload = go;
img.src = "https://i.imgur.com/EWoZkZm.jpg";
function go() {
var me = this,
stepEl = document.querySelector("input"),
stepTxt = document.querySelector("span"),
c = document.querySelector("canvas"),
ctx = c.getContext("2d"),
corners = [
{x: 100, y: 20}, // ul
{x: 520, y: 20}, // ur
{x: 520, y: 380}, // br
{x: 100, y: 380} // bl
],
radius = 10, cPoint, timer, // for mouse handling
step = 4; // resolution
update();
// render image to quad using current settings
function render() {
var p1, p2, p3, p4, y1c, y2c, y1n, y2n,
w = img.width - 1, // -1 to give room for the "next" points
h = img.height - 1;
ctx.clearRect(0, 0, c.width, c.height);
for(y = 0; y < h; y += step) {
for(x = 0; x < w; x += step) {
y1c = lerp(corners[0], corners[3], y / h);
y2c = lerp(corners[1], corners[2], y / h);
y1n = lerp(corners[0], corners[3], (y + step) / h);
y2n = lerp(corners[1], corners[2], (y + step) / h);
// corners of the new sub-divided cell p1 (ul) -> p2 (ur) -> p3 (br) -> p4 (bl)
p1 = lerp(y1c, y2c, x / w);
p2 = lerp(y1c, y2c, (x + step) / w);
p3 = lerp(y1n, y2n, (x + step) / w);
p4 = lerp(y1n, y2n, x / w);
ctx.drawImage(img, x, y, step, step, p1.x, p1.y, // get most coverage for w/h:
Math.ceil(Math.max(step, Math.abs(p2.x - p1.x), Math.abs(p4.x - p3.x))) + 1,
Math.ceil(Math.max(step, Math.abs(p1.y - p4.y), Math.abs(p2.y - p3.y))) + 1)
}
}
}
function lerp(p1, p2, t) {
return {
x: p1.x + (p2.x - p1.x) * t,
y: p1.y + (p2.y - p1.y) * t}
}
/* Stuff for demo: -----------------*/
function drawCorners() {
ctx.strokeStyle = "#09f";
ctx.lineWidth = 2;
ctx.beginPath();
// border
for(var i = 0, p; p = corners[i++];) ctx[i ? "lineTo" : "moveTo"](p.x, p.y);
ctx.closePath();
// circular handles
for(i = 0; p = corners[i++];) {
ctx.moveTo(p.x + radius, p.y);
ctx.arc(p.x, p.y, radius, 0, 6.28);
}
ctx.stroke()
}
function getXY(e) {
var r = c.getBoundingClientRect();
return {x: e.clientX - r.left, y: e.clientY - r.top}
}
function inCircle(p, pos) {
var dx = pos.x - p.x,
dy = pos.y - p.y;
return dx*dx + dy*dy <= radius * radius
}
// handle mouse
c.onmousedown = function(e) {
var pos = getXY(e);
for(var i = 0, p; p = corners[i++];) {if (inCircle(p, pos)) {cPoint = p; break}}
}
window.onmousemove = function(e) {
if (cPoint) {
var pos = getXY(e);
cPoint.x = pos.x; cPoint.y = pos.y;
cancelAnimationFrame(timer);
timer = requestAnimationFrame(update.bind(me))
}
}
window.onmouseup = function() {cPoint = null}
stepEl.oninput = function() {
stepTxt.innerHTML = (step = Math.pow(2, +this.value));
update();
}
function update() {render(); drawCorners()}
}Run Code Online (Sandbox Code Playgroud)
body {margin:20px;font:16px sans-serif}
canvas {border:1px solid #000;margin-top:10px}Run Code Online (Sandbox Code Playgroud)
<label>Step: <input type=range min=0 max=5 value=2></label><span>4</span><br>
<canvas width=620 height=400></canvas>Run Code Online (Sandbox Code Playgroud)