Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to combine element transformations?

In HTML5 I want to implement transform functionality to canvas element, so user can translate (move), scale (zoom in/out) and rotate the canvas element. Each such tranformation can be done with different transform-origin point.

First tranformation is easy:

function transform(el, value, origin) {
  el.style.Transform = value;
  el.style.MozTransform = value;
  el.style.msTransform = value;
  el.style.OTransform = value;
  el.style.webkitTransform = value;
  el.style.TransformOrigin = origin;
  el.style.MozTransformOrigin = origin;
  el.style.msTransformOrigin = origin;
  el.style.OTransformOrigin = origin;
  el.style.webkitTransformOrigin = origin;
}

transform(myCanvas, 'translate('+ dx +'px, ' + dy + 'px) ' +
                    'scale(' + zoom + ', ' + zoom + ') ' +
                    'rotate(' + angle + 'deg)',
                    cx + 'px ' + cy + 'px');

User will move or zoom or rotate the element, not everything at once, so some parameters of tranformation will stay default (dx = 0, dy = 0, zoom = 1, angle = 0).

After such tranformation, if user wants to make another transformation (and antother, and another...), how can I combine (dx1, dy1, zoom1, angle1, cx1, cy1) with (dx2, dy2, zoom2, angle2, cx2, cy2) to get final values that can be combine later with new tranformation parameters? I cannot append another tranformation to transform parameter, because tranform-origin may be different. Is there a formula how to combine tranformations with different transform-origin points?

like image 322
Ωmega Avatar asked Dec 26 '22 21:12

Ωmega


2 Answers

You don't have to learn matrix math. According to the CSS Transform specification

  1. Start with the identity matrix.
  2. Translate by the computed X, Y and Z values of ‘transform-origin’.
  3. Multiply by each of the transform functions in ‘transform’ property in turn
  4. Translate by the negated computed X, Y and Z values of ‘transform-origin’

In other words, transform-origin: A; transform: B is the same as transform: translate(-A) B translate(A). (Transformations apply from right to left, so the first thing you want to happen goes at the end.)

So use the above rules to eliminate transform-origin and now you just have plain transforms you can concatenate.

Example:

  1. transform-origin: 5px 5px; transform: translate(10px, 40px)
  2. transform-origin: 25px 30px; transform: scale(2)
  3. transform-origin: 10px 10px; transform: rotate(30deg)

becomes

  1. transform: translate(-5px, -5px) translate(10px, 40px) translate(5px, 5px)
  2. transform: translate(-25px, -30px) scale(2) translate(25px, 30px)
  3. transform: translate(-10px, -10px) rotate(30deg) translate(10px, 10px)

Now you can combine them since they all agree on the origin (i.e., no origin)

transform: translate(-5px, -5px) translate(10px, 40px) translate(5px, 5px) translate(-25px, -30px) scale(2) translate(25px, 30px) translate(-10px, -10px) rotate(30deg) translate(10px, 10px)

Of course you can collapse the consecutive translations if you want

transform: translate(-15px, 10px) scale(2) translate(15px, 20px) rotate(30deg) translate(10px, 10px)

Or you can dig out your math textbook and compute the final transformation matrix.

Edit: Transforms apply right to left.

like image 80
Raymond Chen Avatar answered Dec 29 '22 10:12

Raymond Chen


You'd have to deal with matrix transformations.

Every linear operation can be represented with a 3x3 matrix and a 3x1 vector that you can apply on the point of the plane. If p is a point, M the matrix and q the other vectory, every linear transformation can be represented as Mp + q.

If you have a 2d point, then its vector will be [x; y; 1] (a vertical vector), while the matrix can be of several form.

For translations, the matrix is just the identity matrix. The vector q is the translation vector.

For scaling, M is like

    [a 0 0]
M = [0 b 0]
    [0 0 1]

where a and b are scaling factor for x and y respectively. The vector q is null.

For rotations, say of an angle a, you'll get

    [cos(a) -sin(a) 0]
M = [sin(a)  cos(a) 0]
    [  0       0    1]

and q is null again.

There are matrices for skewing, too. So, if you have to apply three consecutive transformations, you'll have to apply these kind of linear transformations. Your problem is that you have to deal with origins, too, so you'll have to subtract the vector o from p, than apply M, add q and then o again.

Let's say you have these transformation (M1, q1, o1) and (M2, q2, o2). When you apply the first one you get

p1 = M1 * (p - o1) + q1 + o1

Then you have to apply the second transformation:

p2 = M2 * (p1 - o2) + q2 + o2

In the end you'll get:

p2 = M2 * (M1 * (p - o1) + q1 + o1 - o2) + q2 + o2

And so on with and eventual third (M3, q3, o3).

A mess? It looks like. But things can be simplyfied a bit if you know how the matrices look like.

Now, do the math, and apply it to transform: matrix(a, b, c, d, tx, ty).

like image 30
MaxArt Avatar answered Dec 29 '22 09:12

MaxArt