Подтвердить что ты не робот

Как исказить изображение, подобное этому

Я хочу перекосить изображение вроде этого, какие параметры мне нужно установить для context.setTransform? enter image description here

4b9b3361

Ответ 1

Вы не можете достичь этого с помощью одного двумерного преобразования.

2D-преобразование позволяет вам перекосить изображение "вверх" или "вниз", передавая касательную угла наклона во втором аргументе к setTransform(), но вы хотите выполнять симметрично (в результате "ближняя" и/или "дальняя" деформация). Для этого вам понадобится 3D-преобразование.

Однако вы можете эмулировать один и тот же результат, нарезая изображение на несколько горизонтальных "полос" и применяя другое преобразование при рендеринге каждого диапазона. Полосы, расположенные дальше от половины изображения, будут наносить более сильные углы наклона. Что-то вроде:

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

Обратите внимание, что полоса составляет два пиксела, а не один, чтобы избежать эффекта муара.

Вы можете увидеть результаты в эту скрипту.

Ответ 2

Здесь функция, которую я написал, когда играл с рендерингом псевдо-3d-перспективы с JS.

В отличие от функций преобразования на основе полосы (что, по общему признанию, отлично подходит для большинства стандартных случаев использования), эта функция использует матрицу из 4 углов для определения пользовательского четырехугольника, к которому должен быть преобразован исходный прямоугольник. Это добавляет некоторую гибкость и может быть использовано для визуализации пользовательских трапеций для горизонтальной перспективы "покраска на стене" и вертикальной перспективы "ковер на полу" (а также асимметричных четырехглавых элементов для еще более трехмерного восприятия).

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

Здесь, как его называть:

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

Несмотря на все вычисления в пикселях, он действительно эффективен, и он выполняет свою работу:

преобразованное изображение

... но могут быть более элегантные способы сделать это.

Ответ 3

Существует метод преобразования прямоугольника в трапецию, см. этот ответ. Однако вам нужно будет использовать это на каждом пикселе.

Вы также можете нарезать изображение на вертикальные полосы шириной 1 пиксель, а затем растянуть каждую полосу от ее центра.

Предположим, что это приводит к w полоскам, и вы хотите, чтобы левый край трапеции был на 80% от правого края, тогда

для полосы n растяжение, которое должно быть равно 1 + n/(4w)