1

我需要能够在画布中的图像顶部绘制正方形。我还需要能够缩放和旋转图像,同时保持绘制的正方形的纵横比相同且位于相同的位置。如果画布旋转,我仍然希望能够缩放或移动正方形,而图像不会恢复到原始位置。就目前而言,我可以缩放和旋转在画布上绘制的图像,但是当我旋转然后缩放时,它会恢复到原始位置。我还可以移动画在画布上的正方形。正方形在缩放时保持相同的纵横比。我需要帮助的问题是让图像在缩放时保持旋转,以及让正方形在旋转图像时保持在同一位置,同时仍然允许它们移动。现在当我旋转画布时,

我对 jsFiddle 不太熟悉,但我用我所有的 JS创建了一个。它不显示图像,但在我的示例应用程序中显示。我正在加载 js 文件,<canvas>但我不知道如何在 jsFiddle 中执行此操作。如果你能帮我让它显示图像,你应该能够看到它现在是如何工作的。

提前致谢!

编辑:添加了我的 JS

var startScale = 1;
var scale = 0.8;
var $docCanvas = $('#docCanvas')[0];
var cxt = $docCanvas.getContext('2d');
var canvasWidth;
var canvasHeight;
var imageObj = new Image();
var degreesToRotate = 0;
var imageScaledWidth;
var imageScaledHeight;
var canvasState = new CanvasState($docCanvas);


function init(imageSrc) {
    imageObj.onload = function () {
        $docCanvas.width = canvasWidth = imageObj.width;
        $docCanvas.height = canvasHeight = imageObj.height;
        imageScaledHeight = imageObj.height;
        imageScaledWidth = imageObj.width;

        cxt.drawImage(imageObj, 0, 0);
    };
    canvasState.addShape(new Shape(40, 40, 150, 50)); //default is grey
    imageObj.src = imageSrc;
}

$('#zoomIn').click(function () {
    scale = 1.25;
    resizeImage(scale);
});

$('#zoomOut').click(function () {
    scale = 0.8;
    resizeImage(scale);
});

$('#rotate').click(function () {
    degreesToRotate += 90;
    rotateImage(degreesToRotate);
});

function resizeImage(scale) {
    var heightRemainder = 0;
    var widthRemainder = 0;
    cxt.clearRect(0, 0, $docCanvas.width, $docCanvas.height);

    cxt.save();
    canvasState.valid = false;
    imageScaledWidth = imageScaledWidth * scale;
    imageScaledHeight = imageScaledHeight * scale;
    if (imageScaledWidth % 1 !== 0) {
        widthRemainder = imageScaledWidth % 1;
        imageScaledWidth = Math.round(imageScaledWidth);
    }

    if (imageScaledHeight % 1 !== 0) {
        heightRemainder = imageScaledHeight % 1;
        imageScaledHeight = Math.round(imageScaledHeight);
    }

    $docCanvas.width = imageScaledWidth;
    $docCanvas.height = imageScaledHeight;
    $.each(canvasState.shapes, function () {
        var shape = this;
        shape.resize(scale, widthRemainder, heightRemainder);
    });

    canvasState.draw(0, 0);


    cxt.restore();
}

function rotateImage(angle) {
    cxt.clearRect(0, 0, $docCanvas.width, $docCanvas.height);
    cxt.save();
    var centerWidth = imageScaledWidth / 2;
    var centerHeight = imageScaledHeight / 2;
    //move context to the center of the image
    cxt.translate(centerWidth, centerHeight);
    canvasState.valid = false;

    //rotate around the center of the image
    //the canvas rotate() method takes in an angle in radians.
    cxt.rotate(angle * Math.PI / 180);

    $.each(canvasState.shapes, function () {
        var shape = this;
        shape.rotate(angle);
    });

    canvasState.draw(-centerWidth, -centerHeight);

    cxt.restore();
}

function CanvasState(canvas) {
    // **** First some setup! **** 

    this.canvas = canvas;
    this.width = canvas.width;
    this.height = canvas.height;
    this.ctx = canvas.getContext('2d');

    // This complicates things a little but but fixes mouse co-ordinate problems
    // when there's a border or padding. See getMouse for more detail
    var stylePaddingLeft, stylePaddingTop, styleBorderLeft, styleBorderTop;
    if (document.defaultView && document.defaultView.getComputedStyle) {
        this.stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingLeft'], 10) || 0;
        this.stylePaddingTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingTop'], 10) || 0;
        this.styleBorderLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderLeftWidth'], 10) || 0;
        this.styleBorderTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderTopWidth'], 10) || 0;
    }

    // Some pages have fixed-position bars (like the stumbleupon bar) at the top or left of the page
    // They will mess up mouse coordinates and this fixes that
    var html = document.body.parentNode;
    this.htmlTop = html.offsetTop;
    this.htmlLeft = html.offsetLeft;

    // **** Keep track of state! ****

    this.valid = false; // when set to false, the canvas will redraw everything
    this.shapes = []; // the collection of things to be drawn
    this.dragging = false; // Keep track of when we are dragging

    // the current selected object. In the future we could turn this into an array for multiple selection
    this.selection = null;
    this.dragoffx = 0; // See mousedown and mousemove events for explanation
    this.dragoffy = 0;

    // **** Then events! ****

    // This is an example of a closure!
    // Right here "this" means the CanvasState. But we are making events on the Canvas itself,
    // and when the events are fired on the canvas the variable "this" is going to mean the canvas!
    // Since we still want to use this particular CanvasState in the events we have to save a reference to it.
    // This is our reference!
    var myState = this;

    //fixes a problem where double clicking causes text to get selected on the canvas
    canvas.addEventListener('selectstart', function (e) {
        e.preventDefault();
        return false;
    }, false);

    // Up, down, and move are for dragging
    canvas.addEventListener('mousedown', function (e) {
        var mouse = myState.getMouse(e);
        var mx = mouse.x;
        var my = mouse.y;
        var shapes = myState.shapes;
        var l = shapes.length;
        for (var i = l - 1; i >= 0; i--) {
            if (shapes[i].contains(mx, my)) {
                var mySel = shapes[i];
                // Keep track of where in the object we clicked
                // so we can move it smoothly (see mousemove)
                myState.dragoffx = mx - mySel.x;
                myState.dragoffy = my - mySel.y;
                myState.dragging = true;
                myState.selection = mySel;
                myState.valid = false;
                return;
            }
        }
        // havent returned means we have failed to select anything.
        // If there was an object selected, we deselect it
        if (myState.selection) {
            myState.selection = null;
            myState.valid = false; // Need to clear the old selection border
        }
    }, true);

    canvas.addEventListener('mousemove', function (e) {
        if (myState.dragging) {
            var mouse = myState.getMouse(e);
            // We don't want to drag the object by its top-left corner, we want to drag     it
            // from where we clicked. Thats why we saved the offset and use it here
            myState.selection.x = mouse.x - myState.dragoffx;
            myState.selection.y = mouse.y - myState.dragoffy;
            myState.valid = false; // Something's dragging so we must redraw
        }
    }, true);

    canvas.addEventListener('mouseup', function (e) {
        myState.dragging = false;
    }, true);

    // double click for making new shapes
    //canvas.addEventListener('dblclick', function (e) {
    //  var mouse = myState.getMouse(e);
    //  myState.addShape(new Shape(mouse.x - 10, mouse.y - 10, 20, 20, 'rgba(0,255,0,.6)'));
    //}, true);

    // **** Options! ****

    this.selectionColor = '#CC0000';
    this.selectionWidth = 2;
    this.interval = 30;
    setInterval(function () {
        myState.draw(0, 0);
    }, myState.interval);
}

CanvasState.prototype.addShape = function (shape) {
    this.shapes.push(shape);
    this.valid = false;
};

CanvasState.prototype.clear = function () {
    this.ctx.clearRect(0, 0, this.width, this.height);
};

// While draw is called as often as the INTERVAL variable demands,
// It only ever does something if the canvas gets invalidated by our code
CanvasState.prototype.draw = function (centerWidth, centerHeight) {
    // if our state is invalid, redraw and validate!
    if (!this.valid) {
        var ctx = this.ctx;
        var shapes = this.shapes;
        this.clear();

        ctx.drawImage(imageObj, centerWidth, centerHeight, imageScaledWidth,     imageScaledHeight);

        // ** Add stuff you want drawn in the background all the time here **

        // draw all shapes
        var l = shapes.length;
        for (var i = 0; i < l; i++) {
            var shape = shapes[i];
            // We can skip the drawing of elements that have moved off the screen:
            if (shape.x > imageScaledWidth || shape.y > imageScaledHeight || shape.x +     shape.w < 0 || shape.y + shape.h < 0) continue;
            shapes[i].draw(ctx);
        }

        // draw selection
        // right now this is just a stroke along the edge of the selected Shape
        if (this.selection !== null) {
            ctx.strokeStyle = this.selectionColor;
            ctx.lineWidth = this.selectionWidth;
            var mySel = this.selection;
            ctx.strokeRect(mySel.x, mySel.y, mySel.w, mySel.h);
        }

        // ** Add stuff you want drawn on top all the time here **

        this.valid = true;
    }
};


// Creates an object with x and y defined, set to the mouse position relative to the state's canvas
// If you wanna be super-correct this can be tricky, we have to worry about padding and borders
CanvasState.prototype.getMouse = function (e) {
    var element = this.canvas,
    offsetX = 0,
    offsetY = 0,
    mx, my;

    // Compute the total offset
    if (element.offsetParent !== undefined) {
        do {
            offsetX += element.offsetLeft;
            offsetY += element.offsetTop;
        } while ((element = element.offsetParent));
    }

    // Add padding and border style widths to offset
    // Also add the <html> offsets in case there's a position:fixed bar
    offsetX += this.stylePaddingLeft + this.styleBorderLeft + this.htmlLeft;
    offsetY += this.stylePaddingTop + this.styleBorderTop + this.htmlTop;

    mx = e.pageX - offsetX;
    my = e.pageY - offsetY;

    // We return a simple javascript object (a hash) with x and y defined
    return {
        x: mx,
        y: my
    };
};

// Constructor for Shape objects to hold data for all drawn objects.
// For now they will just be defined as rectangles.
function Shape(x, y, w, h, fill) {
    // This is a very simple and unsafe constructor. 
    // All we're doing is checking if the values exist.
    // "x || 0" just means "if there is a value for x, use that. Otherwise use 0."
    this.x = x || 0;
    this.y = y || 0;
    this.w = w || 1;
    this.h = h || 1;
    this.fill = fill || '#000000';
}

// Draws this shape to a given context
Shape.prototype.draw = function (ctx) {
    ctx.fillStyle = this.fill;
    ctx.fillRect(this.x, this.y, this.w, this.h);
};

// Determine if a point is inside the shape's bounds
Shape.prototype.contains = function (mx, my) {
    // All we have to do is make sure the Mouse X,Y fall in the area between
    // the shape's X and (X + Height) and its Y and (Y + Height)
    return (this.x <= mx) && (this.x + this.w >= mx) && (this.y <= my) && (this.y + this.h >= my);
};


Shape.prototype.resize = function (scale, widthRemainder, heightRemainder) {
    if (widthRemainder > 4) {
        this.x = this.x * scale + widthRemainder;
    } else {
        this.x = this.x * scale - widthRemainder;
    }
    if (heightRemainder > 4) {
        this.y = this.y * scale + heightRemainder;
    } else {
        this.y = this.y * scale - heightRemainder;
    }
    this.h = this.h * scale;
    this.w = this.w * scale;
};

Shape.prototype.rotate = function (angle) {
    var temp;
    if (angle == 90 || angle == 270) {
        temp = this.w;
        this.w = this.h;
        this.h = temp;
    }
};
4

2 回答 2

1

为此使用两幅画布——这样可以避免很多头痛。

当您平移画布时,整个坐标系也会被平移。这会影响您接下来绘制的内容(画布顶部的正方形)。

使用相同画布的一种选择是反计算位置。这在仅进行平移和缩放时并不难,但是在进行旋转时会稍微复杂一些(可行但会带来头痛的部分)。

更简单的解决方案是创建两个堆叠在一起的画布。通过创建具有位置的父元素relative并使用绝对位置将两个画布放入其中来做到这一点(还有其他方法,但现在保持简单)。

现在您可以在底部画布中旋转和绘制图像,并在上部画布上绘制矩形、跟踪移动等。

但是,有一个缺点,那就是如果您使用这些方块对它们所在的图像执行任何操作。然后你回来计算计数器点。

要计算分数,您需要以累积方式跟踪所有翻译。假设这已经完成,您可以使用一些代码,例如(来自我的easyCanvas 项目):

function calcCoords(x, y) {

    var r = me.canvas.getBoundingClientRect(), cx, cy, ang, l;

    x = (x - r.left - me.deltaX) / zoomX;
    y = (y - r.top - me.deltaY) / zoomY;

    if (rotation !== 0) {

        cx = me.width * me.pivotX / zoomX;
        cy = me.height * me.pivotY / zoomY;

        ang = (getAngle(cx, cy, x, y) - rotation) * deg2rad;
        l = getDist(cx, cy, x, y);

        x = cx + l  * Math.cos(ang);
        y = cy + l  * Math.sin(ang);

    }

    return [x, y];
}

function getAngle(x1, y1, x2, y2) {

    var ang = Math.atan2(y2 - y1, x2 - x1) * rad2deg;
    if (ang < 0) ang += 360;

    return ang;
}

您可以通过调用来使用它:

var newCoords = calcCoord(originalX, originalY);
var newX = newCoords[0];
var newY = newCoords[1];

zoomX/Y是当前规模。
pivotX/Y是当前枢轴(通常为 0.5)
width/height是画布
rotation的 累积角度(以度为单位)

要查看此操作,您可以从上面的链接转到此示例:http:
//abdiassoftware.com/easycanvas/samples/sample_basicpaintext.html

(为获得最佳效果,请使用 Chrome)。

于 2013-07-17T16:18:54.657 回答
0

如果你使用或可以使用 CSS3,它有一个内置的功能来旋转元素

div
{
    transform:rotate(30deg);
}

它还具有缩放功能,因此您可以重新缩放元素以“放大”和缩小您也可以使用本教程来帮助您http://www.w3schools.com/css3/

于 2013-07-17T14:40:26.713 回答