heygrady / transform

jQuery 2d transformation plugin
437 stars 87 forks source link

Transform: calculating the new top and left of the transformed element #43

Closed hlubovac closed 6 years ago

hlubovac commented 11 years ago

Maybe I'm missing something, but I think your calculation involving matrices seems to be too complex - and it visibly doesn't [always] provide good results.

It is pretty obvious that the resulting element is wrapped in imaginary (invisible) shape that is always a square, which size can be easily calculated using Pythagorean theorem, and which offset can also be easily calculated (using the fact that its top-left corner is at the same coordinates as the top-left corner of original element and the fact that they need to be centered relative to each other). I came up with this, which gives me visibly correct results:

// calculate offset for IE7 and IE8 var squareSideLength = Math.sqrt(Math.pow(elementWidth, 2) / 2) + Math.sqrt(Math.pow(elementHeight, 2) / 2); var offsetLeft = -1 * parseInt((squareSideLength - elementWidth) / 2, 10); var offsetTop = -1 * parseInt((squareSideLength - elementHeight) / 2, 10);

I ended up using your example for calculating args for Matrix, and that code above to calculate offset.

Thank you for sharing. You saved me a lot of time.

hlubovac commented 11 years ago

What I said is wrong: I made that conclusion incorrectly based on one special test case (angle 45). However, I would think dimensions of the transformed rectangle can be calculated using trigonometry (sin A = b / c; cos A = a / c). Finally, since location (x, y) of the transformed rectangle is known (same as x and y of the original element), and the rule of how other browsers position it (centered relative to the original element), then it should be relatively easy to figure out the unknown offset.

hlubovac commented 11 years ago

I came up with this below. It seems accurate to me. I tested with images of different sizes and variety of angles. This is only a part of the routine I'm working on that relates to my comment above.

I ended up using the Matrix function for IE9 also, because I want to flip images that are rotated, and IE's "filter: FlipH/FlipV" in conjunction with Matrix filter produced black background in corners of transformed image, which I didn't like. So, I'm using Matrix for all IE's for both rotating and flipping. That part isn't showing here, but I wanted to mention how this works for IE9, as well as for IE8 and IE7.

I hope I'm not off-topic, as - based on the example (https://github.com/heygrady/transform/wiki/correcting-transform-origin-and-translate-in-ie) - I understood that to calculate the offset that I was looking for it was suggested to use another library (Sylvester), then some really complex code involving matrices, and then the usage of word "approximate" (related to resulting offset) scared me a little, and finally when I tried I also saw visible errors in certain scenarios. I'm a little bit rusty in trigonometry, but I think what I have below is accurate. Perhaps some of the cases that I treated separately can be combined, but ultimately the result would be the same.


var rotationAngle; // degrees; 0 <= rotationAngle <= 360

var elementWidth, elementHeight; // pixel values; size of element before rotating

var transformedWidth, transformedHeight; // transient values [pixel]

if (rotationAngle === 0 || rotationAngle === 180)
{
    transformedWidth = elementWidth;
    transformedHeight = elementHeight;
}
else if (rotationAngle === 90 || rotationAngle === 270)
{
    transformedWidth = elementHeight;
    transformedHeight = elementWidth;
}
else if (rotationAngle < 90)
{
    var rad = rotationAngle * Math.PI / 180;
    transformedWidth = elementHeight * Math.sin(rad) + elementWidth * Math.cos(rad);
    transformedHeight = elementWidth * Math.sin(rad) + elementHeight * Math.cos(rad);
}
else if (rotationAngle < 180)
{
    var rad = (rotationAngle - 90) * Math.PI / 180;
    transformedWidth = elementWidth * Math.sin(rad) + elementHeight * Math.cos(rad);
    transformedHeight = elementHeight * Math.sin(rad) + elementWidth * Math.cos(rad);
}
else if (rotationAngle < 270)
{
    var rad = (rotationAngle - 180) * Math.PI / 180;
    transformedWidth = elementHeight * Math.sin(rad) + elementWidth * Math.cos(rad);
    transformedHeight = elementWidth * Math.sin(rad) + elementHeight * Math.cos(rad);
}
else if (rotationAngle < 360)
{
    var rad = (rotationAngle - 270) * Math.PI / 180;
    transformedWidth = elementWidth * Math.sin(rad) + elementHeight * Math.cos(rad);
    transformedHeight = elementHeight * Math.sin(rad) + elementWidth * Math.cos(rad);
}

// Element's rectangle before rotation and its transformed element's rectangle 
// after rotation need to be centered relative to each other. These offset values 
// are how much transformed element needs to be shifted left and up (IE only). 
var offsetLeft = -1 * parseInt((transformedWidth - elementWidth) / 2, 10);
var offsetTop = -1 * parseInt((transformedHeight - elementHeight) / 2, 10);
hlubovac commented 11 years ago

I'm sure you know that IE7 and IE8 are reporting the transformed image size to be the same as the "invisible" transformed-element's size, which is the contrary of what IE9 (even with Matrix filter used) and other browsers are reporting (which is the original image size).

I'm using jQuery. I discovered that - after rotation using Matrix filter - jQuery reports the same as IE7/8. However, native JS reports original-image's size. I stepped through jQuery's width/height functions, but I got lost so I gave up. Anyway, DOMElement.width and DOMElement.height can be used to get the original size after rotation.