WebGL glfx.js 矩阵变换(透视)如果旋转则裁剪图像

WebGL glfx.js matrix transform (perspective) crops the image if it rotates

我正在使用 glfx.js 库,以便使用矩阵变换为我的图像创建透视效果。在我的应用程序中,该系统的工作方式就像 photoshop 的智能对象(在其中渲染平面图像并在渲染后获得透视结果)

glfx.js使用此函数canvas.perspective(before, after)对图像应用矩阵变换,通过分配图像中4个点的前后坐标,并在后台运行Matrix命令进行变换我的形象。

我的问题是,如果应用变换后我想要的结果图像比原始图像大(如果旋转图像会发生这种情况),那么 WebGL canvas 将裁剪我的图像.

看下面fiddle:

https://jsfiddle.net/human_a/o4yrheeq/

window.onload = function() {
    try {
      var canvas = fx.canvas();
    } catch (e) {
      alert(e);
      return;
    }

    // convert the image to a texture
    var image = document.getElementById('image');
    var texture = canvas.texture(image);

    // apply the perspective filter
    canvas.draw(texture).perspective( [0,0,774,0,0,1094,774,1094], [0,389,537,0,732,1034,1269,557] ).update();

    image.src = canvas.toDataURL('image/png');

    // or even if you replace the image with the canvas
    // image.parentNode.insertBefore(canvas, image);
    // image.parentNode.removeChild(image);
};
<script src="https://evanw.github.io/glfx.js/glfx.js"></script>
<img id="image" crossOrigin="anonymous" src="https://images.unsplash.com/photo-1485207801406-48c5ac7286b2?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=600&fit=max&s=9bb1a18da78ab0980d5e7870a236af88">

关于如何使 WebGL canvas 适合旋转后的图像(而不是缩小图像)或以某种方式提取整个图像而不是裁剪后的图像,有什么想法吗?

更多像素

没有覆盖所有的解决方案。这是因为当您从 2D 转换为 3D 时,投影图像的大小可能接近无穷大(接近裁剪可防止无穷大),因此无论您将图像输出多大,总有可能应用一些裁剪。

考虑到这个警告,大多数情况下都有一个解决方案可以避免裁剪。很简单,就是展开canvas来容纳额外的内容。

找到界限

为了简化计算,我将后数组更改为一组标准化点(它们将后坐标表示为图像大小的比例因子)。然后我使用图像大小转换为真实像素坐标。然后我从中计算出纹理需要保持原始图像和投影的最小尺寸。

根据这些信息,我只需创建纹理(作为 canvas)绘制图像。如果需要调整 befor 数组(以防某些投影点为负 space)并应用过滤器。

所以我们有一个具有宽度和高度的图像对象。你有这些点的投影。

// assuming image has been loaded and is ready
var imgW = image.naturalWidth;
var imgH = image.naturalHeight;

设置角数组(之前)

var before = [0, 0, imgW, 0, 0, imgH, imgW, imgH];

投影点。为了更容易处理,我将投影点标准化为图像大小

var projectNorm =  [[0, 0.3556], [0.6938, 0], [0.9457, 0.9452], [1.6395, 0.5091]];

如果你想像在fiddle的后数组中一样使用绝对坐标,请使用以下代码。规范化在 next 之后的代码片段中反转,因此您可以跳过规范化。由于时间紧迫,我刚刚更新了答案。

var afterArray = [0,389,537,0,732,1034,1269,557];
projectNorm = [];
for(var i = 0; i < afterArray.length; i+= 2){
     afterArray.push([afterArray[i] / before[i], afterArray[i + 1] / before[i + 1]]);
}

现在计算投影的大小。这是重要的部分,因为它计算出 canvas.

的大小
var top, left, right, bottom;
top = 0;
left = 0;
bottom = imgH;
right = imgW;
var project = projectNorm.map(p => [p[0] * imgW, p[1] * imgH]);
project.forEach(p => {
    top = Math.min(p[1], top);
    left = Math.min(p[0], left);
    bottom = Math.max(p[1], bottom);
    right = Math.max(p[0], right);
});

现在我们需要的所有数据都已收集到,我们可以创建一个新图像来适应投影。 (假设投影点真实投影)

var texture = document.createElement("canvas");
var ctx = texture.getContext("2d");
texture.width = Math.ceil(right - left);
texture.height = Math.ceil(bottom - top);

在 0,0 绘制图像

ctx.setTransform(1, 0, 0, 1, left, top); // put origin so image is at 0,0
ctx.drawImage(image,0,0);
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform

然后将投影点数组展平

var after = [];
project.forEach(p => after.push(...p));

将所有点移动到正投影中space

after.forEach((p,i) => {
    if (i % 2) {
        before[i] += -top;
        after[i] += -top;
    } else {
        before[i] += -left;
        after[i] += -left;
    }
});

最后一步是创建 glfx.js 个对象并应用过滤器

// create a fx canvas
var canvas = fx.canvas();
// create the texture 
var glfxTexture = canvas.texture(texture);
// apply the filter
canvas.draw(glfxTexture).perspective( before, after ).update();
// show the result on the page
document.body.appendChild(canvas);

演示

使用上述方法演示您的代码片段(对图像加载稍作修改)

// To save time typing I have just kludged a simple load image wait poll
waitForLoaded();
function waitForLoaded(){
    if(image.complete){
        projectImage(image);
    }else{
        setTimeout(waitForLoaded,500);
    }
}
function projectImage(image){
    var imgW = image.naturalWidth;
    var imgH = image.naturalHeight;
    var projectNorm =  [[0, 0.3556], [0.6938, 0], [0.9457, 0.9452], [1.6395, 0.5091]];
    var before = [0, 0, imgW, 0, 0, imgH, imgW, imgH];
    var top, left, right, bottom;
    top = 0;
    left = 0;
    bottom = imgH;
    right = imgW;
    var project = projectNorm.map(p => [p[0] * imgW, p[1] * imgH]);
    project.forEach(p => {
        top = Math.min(p[1], top);
        left = Math.min(p[0], left);
        bottom = Math.max(p[1], bottom);
        right = Math.max(p[0], right);
    });
    var texture = document.createElement("canvas");
    var ctx = texture.getContext("2d");
    texture.width = Math.ceil(right - left);
    texture.height = Math.ceil(bottom - top);
    ctx.setTransform(1, 0, 0, 1, left, top); // put origin so image is at 0,0
    ctx.drawImage(image,0,0);
    ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
    var after = [];
    project.forEach(p => after.push(...p));

    after.forEach((p,i) => {
        if (i % 2) {
           before[i] += -top;
           after[i] += -top;
        } else {
            before[i] += -left;
            after[i] += -left;
        }
    });
    // create a fx canvas
    var canvas = fx.canvas();
    // create the texture 
    var glfxTexture = canvas.texture(texture);
    // apply the filter
    canvas.draw(glfxTexture).perspective( before, after ).update();
    // show the result on the page
    document.body.appendChild(canvas);
}    
#image {
  display : none;
}
<script src="https://evanw.github.io/glfx.js/glfx.js"></script>
<img id="image" crossOrigin="anonymous" src="https://images.unsplash.com/photo-1485207801406-48c5ac7286b2?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=9bb1a18da78ab0980d5e7870a236af88">

注释和警告

Note that the projection points (after array) do not always match the final corner points of the projected image. If this happens the final image may be clipped.

Note This method only works if the before points represent the exterme corners of the original image. If the points (before) are inside the image then this method may fail.

Warning There is no vetting of the resulting image size. Large Images can cause the browser to become sluggish, and sometimes crash. For production code you should do your best to keep the image size within the limits of the device that is using your code. Clients seldom return to pages that are slow and/or crash.