javascript 如何像这样倾斜图像
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10426887/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
how to skew image like this
提问by coure2011
I want to skew an image like this what params I need to set for context.setTransform?
我想倾斜这样的图像,我需要为 context.setTransform 设置什么参数?
回答by Frédéric Hamidi
You cannot achieve this with a single 2D transform.
您无法通过单个 2D 变换实现此目的。
A 2D transform allows you to skew the image "upwards" or "downwards" by passing the tangent of the skew angle in the second argument to setTransform()
, but you want to perform both in a symmetrical manner (resulting in a "nearwards" and/or "farwards" deformation). You need a 3D transform to do that.
2D 变换允许您通过将第二个参数中的倾斜角的切线传递给 来“向上”或“向下”倾斜图像setTransform()
,但您希望以对称的方式执行这两者(导致“接近”和/或“远”变形)。你需要一个 3D 变换来做到这一点。
However, you can emulate the same result by slicing the image into several horizontal "bands" and applying a different transform when rendering each band. Bands further from the half of the image will be applied stronger skew angles. Something like:
但是,您可以通过将图像切片成多个水平“波段”并在渲染每个波段时应用不同的变换来模拟相同的结果。距离图像一半较远的波段将应用更强的倾斜角度。就像是:
var width = image.width,
height = image.height,
context = $("canvas")[0].getContext("2d");
for (var i = 0; i <= height / 2; ++i) {
context.setTransform(1, -0.4 * i / height, 0, 1, 0, 60);
context.drawImage(image,
0, height / 2 - i, width, 2,
0, height / 2 - i, width, 2);
context.setTransform(1, 0.4 * i / height, 0, 1, 0, 60);
context.drawImage(image,
0, height / 2 + i, width, 2,
0, height / 2 + i, width, 2);
}
Note the bands are two pixels high instead of one to avoid a mtheitroade effect.
请注意,波段是两个像素而不是一个像素高,以避免出现莫尔效应。
You can see the results in this fiddle.
你可以在这个 fiddle 中看到结果。
回答by Tom Rubaj
Here's a function I wrote when I was playing with rendering pseudo-3d perspective with JS.
这是我在玩 JS 渲染伪 3d 透视图时编写的一个函数。
Unlike the stripe-based transformation functions (which, admittedly, are perfectly good enough for most standard use cases) this function uses a matrix of 4 corners to define a custom quadrilateral that the original rectangle should be transformed to. This adds some flexibility and can be used to render custom trapezoids for both "painting-on-the-wall" horizontal perspective and "carpet-on-the-floor" vertical perspective (as well as asymetrical quadrilaterals for even more 3d-like feel).
与基于条带的转换函数(不可否认,对于大多数标准用例来说已经足够好)不同,该函数使用 4 个角的矩阵来定义原始矩形应转换为的自定义四边形。这增加了一些灵活性,可用于为“墙上绘画”水平透视和“地板上的地毯”垂直透视(以及不对称的四边形,以获得更像 3d 的感觉)渲染自定义梯形)。
function drawImageInPerspective(
srcImg,
targetCanvas,
//Define where on the canvas the image should be drawn:
//coordinates of the 4 corners of the quadrilateral that the original rectangular image will be transformed onto:
topLeftX, topLeftY,
bottomLeftX, bottomLeftY,
topRightX, topRightY,
bottomRightX, bottomRightY,
//optionally flip the original image horizontally or vertically *before* transforming the original rectangular image to the custom quadrilateral:
flipHorizontally,
flipVertically
) {
var srcWidth=srcImg.naturalWidth;
var srcHeight=srcImg.naturalHeight;
var targetMarginX=Math.min(topLeftX, bottomLeftX, topRightX, bottomRightX);
var targetMarginY=Math.min(topLeftY, bottomLeftY, topRightY, bottomRightY);
var targetTopWidth=(topRightX-topLeftX);
var targetTopOffset=topLeftX-targetMarginX;
var targetBottomWidth=(bottomRightX-bottomLeftX);
var targetBottomOffset=bottomLeftX-targetMarginX;
var targetLeftHeight=(bottomLeftY-topLeftY);
var targetLeftOffset=topLeftY-targetMarginY;
var targetRightHeight=(bottomRightY-topRightY);
var targetRightOffset=topRightY-targetMarginY;
var tmpWidth=Math.max(targetTopWidth+targetTopOffset, targetBottomWidth+targetBottomOffset);
var tmpHeight=Math.max(targetLeftHeight+targetLeftOffset, targetRightHeight+targetRightOffset);
var tmpCanvas=document.createElement('canvas');
tmpCanvas.width=tmpWidth;
tmpCanvas.height=tmpHeight;
var tmpContext = tmpCanvas.getContext('2d');
tmpContext.translate(
flipHorizontally ? tmpWidth : 0,
flipVertically ? tmpHeight : 0
);
tmpContext.scale(
(flipHorizontally ? -1 : 1)*(tmpWidth/srcWidth),
(flipVertically? -1 : 1)*(tmpHeight/srcHeight)
);
tmpContext.drawImage(srcImg, 0, 0);
var tmpMap=tmpContext.getImageData(0,0,tmpWidth,tmpHeight);
var tmpImgData=tmpMap.data;
var targetContext=targetCanvas.getContext('2d');
var targetMap = targetContext.getImageData(targetMarginX,targetMarginY,tmpWidth,tmpHeight);
var targetImgData = targetMap.data;
var tmpX,tmpY,
targetX,targetY,
tmpPoint, targetPoint;
for(var tmpY = 0; tmpY < tmpHeight; tmpY++) {
for(var tmpX = 0; tmpX < tmpWidth; tmpX++) {
//Index in the context.getImageData(...).data array.
//This array is a one-dimensional array which reserves 4 values for each pixel [red,green,blue,alpha) stores all points in a single dimension, pixel after pixel, row after row:
tmpPoint=(tmpY*tmpWidth+tmpX)*4;
//calculate the coordinates of the point on the skewed image.
//
//Take the X coordinate of the original point and translate it onto target (skewed) coordinate:
//Calculate how big a % of srcWidth (unskewed x) tmpX is, then get the average this % of (skewed) targetTopWidth and targetBottomWidth, weighting the two using the point's Y coordinate, and taking the skewed offset into consideration (how far topLeft and bottomLeft of the transformation trapezium are from 0).
targetX=(
targetTopOffset
+targetTopWidth * tmpX/tmpWidth
)
* (1- tmpY/tmpHeight)
+ (
targetBottomOffset
+targetBottomWidth * tmpX/tmpWidth
)
* (tmpY/tmpHeight)
;
targetX=Math.round(targetX);
//Take the Y coordinate of the original point and translate it onto target (skewed) coordinate:
targetY=(
targetLeftOffset
+targetLeftHeight * tmpY/tmpHeight
)
* (1-tmpX/tmpWidth)
+ (
targetRightOffset
+targetRightHeight * tmpY/tmpHeight
)
* (tmpX/tmpWidth)
;
targetY=Math.round(targetY);
targetPoint=(targetY*tmpWidth+targetX)*4;
targetImgData[targetPoint]=tmpImgData[tmpPoint]; //red
targetImgData[targetPoint+1]=tmpImgData[tmpPoint+1]; //green
targetImgData[targetPoint+2]=tmpImgData[tmpPoint+2]; //blue
targetImgData[targetPoint+3]=tmpImgData[tmpPoint+3]; //alpha
}
}
targetContext.putImageData(targetMap,targetMarginX,targetMarginY);
}
Here's how to call it:
调用方法如下:
function onLoad() {
var canvas = document.createElement("canvas");
canvas.id = 'canvas';
canvas.width=800;
canvas.height=800;
document.body.appendChild(canvas);
var img = new Image();
img.onload = function(){
//draw the original rectangular image as a 300x300 quadrilateral with its bottom-left and top-right corners skewed a bit:
drawImageInPerspective(
img, canvas,
//coordinates of the 4 corners of the quadrilateral that the original rectangular image will be transformed onto:
0, 0, //top left corner: x, y
50, 300, //bottom left corner: x, y - position it 50px more to the right than the top right corner
300, 50, //top right corner: x, y - position it 50px below the top left corner
300, 300, //bottom right corner: x,y
false, //don't flip the original image horizontally
false //don't flip the original image vertically
);
}
img.src="img/rectangle.png";
}
Despite all the per-pixel calculations it is actually quite efficient, and it gets the job done:
尽管进行了每像素计算,但它实际上非常有效,并且可以完成工作:
...but there may be more elegant ways to do it.
...但可能有更优雅的方法来做到这一点。
回答by jing3142
There is a method of transforming a rectangle to a trapezium see this stack overflow answer. However you would need to use this on each pixel.
有一种将矩形转换为梯形的方法,请参阅此堆栈溢出答案。但是,您需要在每个像素上使用它。
You could also slice the image into vertical strips 1 pixel wide and then stretch each strip from its centre.
您还可以将图像切成 1 像素宽的垂直条带,然后从其中心拉伸每个条带。
Suppose this leads to w strips and you want the left hand edge of the trapezium to be 80% of the right hand edge then
假设这导致 w 条,并且您希望梯形的左手边是右手边的 80%,然后
for strip n the stretch whould be 1+n/(4w)
对于条带 n,拉伸应该是 1+n/(4w)