Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
234 views
in Technique[技术] by (71.8m points)

math - Compute untransformed element's bounding rectangle given the element's current bounding rectangle and its transform matrix

Given a DOM element el to which we've applied the transform matrix M (an instance of DOMMatrix), and its current bounding rectangle rect, how can we obtain the bounding rectangle rect_init that corresponds to the untransformed element?

i.e. given this code:

let rect = el.getBoundingClientRect();
el.style.transform = '';
let rect_init = el.getBoundingClientRect();
el.style.transform = M.toString();

Knowing rect and M, can we obtain rect_init?

The matrix contains only translation, uniform scale, and rotation transforms. In the illustration below the element is represented by the blue rectangle, and its post-transform bounding rectangle is the red one.

There's an older, related question whose answers don't seem to cover all combinations of translation, scale and rotation.

enter image description here

In the demo below, given M and current bbox, I'm looking for initial bbox.

let target_element = document.querySelector('#target');
let M = new DOMMatrix()
  .translate(20, 30)
  .rotate(30)
  .scale(1.25);

let init_rect = target_element.getBoundingClientRect();

target_element.style.transform = M.toString();

let rect = target_element.getBoundingClientRect();

document.querySelector('#rect-init').textContent = serialize(init_rect);
document.querySelector('#rect').textContent = serialize(rect);
document.querySelector('#matrix').textContent = M.toString();

function serialize(rect) {
  return `x: ${rect.x}; y: ${rect.y}, w: ${rect.width}, h: ${rect.height}`;
}
#target {
  background: red;
  width: 200px;
  height: 100px;
  position: absolute;
  left: 30px;
  top: 50px;
}

#info {
  background: #eee;
  padding: 1em;
  margin-top: 250px;
  font: 0.9em monospace;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <div id='target'>Target</div>
  
  <dl id='info'>
    <dt>initial bbox: </dt>
    <dd id='rect-init'></dd>
    <dt>current bbox: </dt>
    <dd id='rect'></dd>
    <dt>M:</dt>
    <dd id='matrix'></dd>
  </dl>
</body>
</html>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I managed to get it working ...

so I did a small C++/OpenGL example where I test this:

//---------------------------------------------------------------------------
//--- input data ------------------------------------------------------------
//---------------------------------------------------------------------------
double m[16]=       // matrix
    {
    1.0825317547305484,-0.6249999999999999,0.0,20.0,
    0.6249999999999999, 1.0825317547305484,0.0,30.0,
    0.0               , 0.0               ,1.0,0.0 ,
    0.0               , 0.0               ,0.0,1.0
    };
double a[4][3]=     // your un-transformed rectangle
    {
    { 30    ,50    ,0 },
    { 30+200,50    ,0 },
    { 30+200,50+100,0 },
    { 30    ,50+100,0 },
    };
//---------------------------------------------------------------------------
//--- just matrix and vector math you can ignore this -----------------------
//---------------------------------------------------------------------------
void  vector_copy      (double *c,double *a) { for(int i=0;i<3;i++) c[i]=a[i]; }
void  matrix_mul       (double *c,double *a,double *b)  // c[16] = a[16]*b[16]
    {
    double q[16];
    q[ 0]=(a[ 0]*b[ 0])+(a[ 1]*b[ 4])+(a[ 2]*b[ 8])+(a[ 3]*b[12]);
    q[ 1]=(a[ 0]*b[ 1])+(a[ 1]*b[ 5])+(a[ 2]*b[ 9])+(a[ 3]*b[13]);
    q[ 2]=(a[ 0]*b[ 2])+(a[ 1]*b[ 6])+(a[ 2]*b[10])+(a[ 3]*b[14]);
    q[ 3]=(a[ 0]*b[ 3])+(a[ 1]*b[ 7])+(a[ 2]*b[11])+(a[ 3]*b[15]);
    q[ 4]=(a[ 4]*b[ 0])+(a[ 5]*b[ 4])+(a[ 6]*b[ 8])+(a[ 7]*b[12]);
    q[ 5]=(a[ 4]*b[ 1])+(a[ 5]*b[ 5])+(a[ 6]*b[ 9])+(a[ 7]*b[13]);
    q[ 6]=(a[ 4]*b[ 2])+(a[ 5]*b[ 6])+(a[ 6]*b[10])+(a[ 7]*b[14]);
    q[ 7]=(a[ 4]*b[ 3])+(a[ 5]*b[ 7])+(a[ 6]*b[11])+(a[ 7]*b[15]);
    q[ 8]=(a[ 8]*b[ 0])+(a[ 9]*b[ 4])+(a[10]*b[ 8])+(a[11]*b[12]);
    q[ 9]=(a[ 8]*b[ 1])+(a[ 9]*b[ 5])+(a[10]*b[ 9])+(a[11]*b[13]);
    q[10]=(a[ 8]*b[ 2])+(a[ 9]*b[ 6])+(a[10]*b[10])+(a[11]*b[14]);
    q[11]=(a[ 8]*b[ 3])+(a[ 9]*b[ 7])+(a[10]*b[11])+(a[11]*b[15]);
    q[12]=(a[12]*b[ 0])+(a[13]*b[ 4])+(a[14]*b[ 8])+(a[15]*b[12]);
    q[13]=(a[12]*b[ 1])+(a[13]*b[ 5])+(a[14]*b[ 9])+(a[15]*b[13]);
    q[14]=(a[12]*b[ 2])+(a[13]*b[ 6])+(a[14]*b[10])+(a[15]*b[14]);
    q[15]=(a[12]*b[ 3])+(a[13]*b[ 7])+(a[14]*b[11])+(a[15]*b[15]);
    for(int i=0;i<16;i++) c[i]=q[i];
    }
void  matrix_mul_vector(double *c,double *a,double *b)      // c[3] = a[16]*b[3]
    {
    double q[3];
    q[0]=(a[ 0]*b[0])+(a[ 4]*b[1])+(a[ 8]*b[2])+(a[12]);
    q[1]=(a[ 1]*b[0])+(a[ 5]*b[1])+(a[ 9]*b[2])+(a[13]);
    q[2]=(a[ 2]*b[0])+(a[ 6]*b[1])+(a[10]*b[2])+(a[14]);
    for(int i=0;i<3;i++) c[i]=q[i];
    }
void  matrix_subdet    (double *c,double *a);               // c[16] = all subdets of a[16]
double matrix_subdet   (          double *a,int r,int s);   //       = subdet(r,s) of a[16]
double matrix_det      (          double *a);               //       = det of a[16]
double matrix_det      (          double *a,double *b);     //       = det of a[16] and subdets b[16]
void  matrix_inv2       (double *c,double *a);              // c[16] = a[16] ^ -1
void  matrix_subdet    (double *c,double *a)
        {
        double   q[16];
        int     i,j;
        for (i=0;i<4;i++)
         for (j=0;j<4;j++)
          q[j+(i<<2)]=matrix_subdet(a,i,j);
        for (i=0;i<16;i++) c[i]=q[i];
        }
double matrix_subdet    (         double *a,int r,int s)
        {
        double   c,q[9];
        int     i,j,k;
        k=0;                            // q = sub matrix
        for (j=0;j<4;j++)
         if (j!=s)
          for (i=0;i<4;i++)
           if (i!=r)
                {
                q[k]=a[i+(j<<2)];
                k++;
                }
        c=0;
        c+=q[0]*q[4]*q[8];
        c+=q[1]*q[5]*q[6];
        c+=q[2]*q[3]*q[7];
        c-=q[0]*q[5]*q[7];
        c-=q[1]*q[3]*q[8];
        c-=q[2]*q[4]*q[6];
        if (int((r+s)&1)) c=-c;       // add signum
        return c;
        }
double matrix_det       (         double *a)
        {
        double c=0;
        c+=a[ 0]*matrix_subdet(a,0,0);
        c+=a[ 4]*matrix_subdet(a,0,1);
        c+=a[ 8]*matrix_subdet(a,0,2);
        c+=a[12]*matrix_subdet(a,0,3);
        return c;
        }
double matrix_det       (         double *a,double *b)
        {
        double c=0;
        c+=a[ 0]*b[ 0];
        c+=a[ 4]*b[ 1];
        c+=a[ 8]*b[ 2];
        c+=a[12]*b[ 3];
        return c;
        }
void  matrix_inv(double *c,double *a)
        {
        double   d[16],D;
        matrix_subdet(d,a);
        D=matrix_det(a,d);
        if (D) D=1.0/D;
        for (int i=0;i<16;i++) c[i]=d[i]*D;
        }
//---------------------------------------------------------------------------
//--- render ----------------------------------------------------------------
//---------------------------------------------------------------------------
void TForm1::draw()
    {
    int i;
    double xs=ClientWidth,ys=ClientHeight; // just my GL view resolution
    double p[3],im[16]; // some temp point and inverse matrix
    double b[4][3];     // your rectangle
    matrix_inv(im,m);   // im=Inverse(m)

    glClear(GL_COLOR_BUFFER_BIT);
    glDisable(GL_CULL_FACE);
    glDisable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glTranslated(-0.5,+0.5,0.0);
    glScaled(2.0/xs,-2.0/ys,1.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // render transformed a and compute its BBOX into b
    glColor3d(0.0,0.5,0.5); // aqua
    glBegin(GL_LINE_LOOP);
    for (i=0;i<4;i++)
        {
        // transform and render rectangle a
        matrix_mul_vector(p,im,a[i]);   // p = inverse(m)*a[i]
        glVertex2dv(p);
        // compute transformed BBOX b[0]=min(p) b[2]=max(p)
        if (i==0)
            {
            vector_copy(b[0],p);
            vector_copy(b[2],p);
            }
        if (b[0][0]>p[0]) b[0][0]=p[0];
        if (b[2][0]<p[0]) b[2][0]=p[0];
        if (b[0][1]>p[1]) b[0][1]=p[1];
        if (b[2][1]<p[1]) b[2][1]=p[1];
        }
    glEnd();
    // convert BBOX b[0],b[2] to rectangle
    b[1][0]=b[2][0];
    b[1][1]=b[0][1];
    b[3][0]=b[0][0];
    b[3][1]=b[2][1];

    // render transformed b
    glColor3d(0.8,0.0,0.0); // red
    glBegin(GL_LINE_LOOP);
    for (i=0;i<4;i++) glVertex2dv(b[i]); glEnd();

    // untransform b to rectangle local coordinates
    for (i=0;i<4;i++) matrix_mul_vector(b[i],m,b[i]);   // b[i] = m*b[i]

    // render a,b in local coordiantes (untransformed)
    glColor3d(0.0,0.25,0.25); // aqua
    glBegin(GL_LINE_LOOP);
    for (i=0;i<4;i++) glVertex2dv(a[i]);
    glEnd();
    glColor3d(0.4,0.0,0.0); // red
    glBegin(GL_LINE_LOOP);
    for (i=0;i<4;i++) glVertex2dv(b[i]);
    glEnd();


    glFlush();
    SwapBuffers(hdc);
    }
//---------------------------------------------------------------------------

And tweaked the transformation notation of yours until it matched your preview:

preview

the more lighter colors correspond to stuff transformed by m and the darker are without transformation (rectangle local).

I figured out that your notation for transformation of p into canvas coordinates is:

p' = Inverse(m)*p

So to transform back you would normaly be done with:

p = m*p`

However if your inverse matrix is just pseudo inverse (like this) to get it working you need to divide the inverted matrix by scale.

So if you got the points of BBOX you just multiply m with them and the result is what you seek.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...