WebGL glfx.js矩阵变换(透视)在图像旋转时裁剪图像



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

glfx.js使用这个函数canvas.perspective(before, after)对图像进行矩阵变换,方法是在图像中的4个点进行前后协调,并在后台运行matrix命令来变换我的图像。

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

看看下面的小提琴:

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画布适合旋转后的图像(而不是使图像变小),或者以某种方式提取整个图像而不是裁剪后的图像,有什么想法吗?

##更多像素

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

有了这个警告,对于大多数情况都有一个可以避免剪裁的解决方案。它非常简单,只需展开画布即可容纳额外的内容。

###查找边界

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

有了这些信息,我只需要创建纹理(作为画布)来绘制图像。如果需要,调整before数组(以防某些投影点位于负空间)并应用过滤器。

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

// 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的after数组中的绝对坐标,请使用以下代码。规范化在接下来的代码段中反转,因此您可以跳过规范化。由于时间不够,我刚刚很快更新了答案。

var i = 0;
var afterArray = [0,389,537,0,732,1034,1269,557];
const projectNorm = [];
while (i < afterArray.length){
projectNorm.push([
before[i] ? afterArray[i] / before[i++] : before[i++],
before[i] ? afterArray[i] / before[i++] : before[i++]
]);
}

现在计算投影的大小。这是计算画布大小的重要部分。

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));

将所有点移动到正投影空间

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">

###钞票和警告

注意投影点(阵列之后)并不总是与投影图像的最终角点匹配。如果发生这种情况,最终图像可能会被剪切。

注意此方法仅在前点表示原始图像的外角时有效。如果(之前的)点在图像内部,则此方法可能会失败。

警告不会对生成的图像大小进行审查。大图像可能会导致浏览器变得迟钝,有时甚至崩溃。对于生产代码,您应该尽最大努力将图像大小保持在使用代码的设备的限制范围内。客户端很少返回速度慢和/或崩溃的页面。

最新更新