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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 09:51:15  来源:igfitidea点击:

how to skew image like this

javascriptcanvas

提问by coure2011

I want to skew an image like this what params I need to set for context.setTransform? enter image description here

我想倾斜这样的图像,我需要为 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:

尽管进行了每像素计算,但它实际上非常有效,并且可以完成工作:

transformed image

变换图像

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