图像处理 - 在精确位置添加带有角点的图像 [英] Image Manipulation - add image with corners in exact positions

查看:19
本文介绍了图像处理 - 在精确位置添加带有角点的图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个图像,它是一个包含这样的框区域的背景:

I have an image which is a background containing a boxed area like this:

我知道那个形状的角的确切位置,我想在其中放置另一个图像.(所以它看起来在盒子里面).

I know the exact positions of the corners of that shape, and I'd like to place another image within it. (So it appears to be inside the box).

我知道 HTML5 画布的 drawImage 方法,但它似乎只支持 x、y、宽度、高度参数而不是精确坐标.我如何将图像以一组特定的坐标绘制到画布上,理想情况下让浏览器本身处理拉伸图像.

I'm aware of the drawImage method for HTML5 canvas, but it seems to only support x, y, width, height parameters rather than exact coordinates. How might I draw an image onto a canvas at a specific set of coordinates, and ideally have the browser itself handle stretching the image.

推荐答案

四边形变换

一种方法是使用四边形变换.它们与 3D 变换不同,如果您想导出结果,它们允许您在画布上进行绘制.

Quadrilateral transform

One way to go about this is to use Quadrilateral transforms. They are different than 3D transforms and would allow you to draw to a canvas in case you want to export the result.

此处显示的示例进行了简化,并在渲染本身上使用了基本的细分和作弊"——也就是说,它绘制了一个小正方形而不是细分单元的形状,但由于尺寸小且在许多非极端情况下,我们可以避免重叠.

The example shown here is simplified and uses basic sub-divison and "cheats" on the rendering itself - that is, it draws in a small square instead of the shape of the sub-divided cell but because of the small size and the overlap we can get away with it in many non-extreme cases.

正确的方法是将形状分成两个三角形,然后在目标位图中逐像素扫描,将点从目标三角形映射到源三角形.如果位置值是小数,您将使用它来确定像素插值(例如双线性 2x2 或双三次 4x4).

The proper way would be to split the shape into two triangles, then scan pixel wise in the destination bitmap, map the point from destination triangle to source triangle. If the position value was fractional you would use that to determine pixel interpolation (f.ex. bi-linear 2x2 or bi-cubic 4x4).

我不打算在这个答案中涵盖所有这些,因为它很快就会超出 SO 格式的范围,但是除非您需要对其进行动画处理,否则该方法可能适用于这种情况(它的性能不足以满足如果你想要高分辨率的话).

I do not intend to cover all this in this answer as it would quickly become out of scope for the SO format, but the method would probably be suitable in this case unless you need to animate it (it is not performant enough for that if you want high resolution).

让我们从一个初始的四边形开始:

Lets start with an initial quadrilateral shape:

第一步是在每个条形 C1-C4 和 C2-C3 上插入 Y 位置.我们将需要当前位置以及下一个位置.我们将使用线性插值(lerp"),使用 t 的归一化值:

The first step is to interpolate the Y-positions on each bar C1-C4 and C2-C3. We're gonna need current position as well as next position. We'll use linear interpolation ("lerp") for this using a normalized value for 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)

这为我们在外部竖线之间和沿着外竖线提供了一条新线.

This gives us a new line between and along the outer vertical bars.

接下来我们需要该行上的 X 位置,包括当前位置和下一个位置.这将为我们提供四个位置,我们将用当前像素填充,按原样或插入它(此处未显示):

Next we need the X positions on that line, both current and next. This will give us four positions we will fill with current pixel, either as-is or interpolate it (not shown here):

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)

xy 将使用整数值作为源图像中的位置.

x and y will be the position in the source image using integer values.

我们可以在循环中使用这个设置,循环遍历源位图中的每个像素.

We can use this setup inside a loop that will iterate over each pixel in the source bitmap.

可以在答案底部找到演示.移动圆形手柄以变换和调整步长值,以查看其对性能和结果的影响.

The demo can be found at the bottom of the answer. Move the circular handles around to transform and play with the step value to see its impact on performance and result.

演示有摩尔纹和其他人工制品,但如前所述,这将是另一天的话题.

The demo will have moire and other artifacts, but as mentioned earlier that would be a topic for another day.

演示截图:

您还可以使用 WebGL 或 Three.js 来设置 3D 环境并渲染到画布.这是后一种解决方案的链接:

You can also use WebGL or Three.js to setup a 3D environment and render to canvas. Here is a link to the latter solution:

以及如何使用纹理映射表面的示例:

and an example of how to use texture mapped surface:

使用这种方法,您还可以将结果导出到画布或图像,但为了提高性能,客户端需要 GPU.

Using this approach will enable you to export the result to a canvas or an image as well, but for performance a GPU is required on the client.

如果您不需要导出或操作结果,我建议使用简单的 CSS 3D 转换,如其他答案所示.

If you don't need to export or manipulate the result I would suggest to use simple CSS 3D transform as shown in the other answers.

/* 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()}
}

body {margin:20px;font:16px sans-serif}
canvas {border:1px solid #000;margin-top:10px}

<label>Step: <input type=range min=0 max=5 value=2></label><span>4</span><br>
<canvas width=620 height=400></canvas>

这篇关于图像处理 - 在精确位置添加带有角点的图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆