xref: /trunk/main/basegfx/source/matrix/b2dhommatrix.cxx (revision 45d1b581a3afed2711c07fe77472516d35fbd7b3)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 // MARKER(update_precomp.py): autogen include statement, do not remove
23 #include "precompiled_basegfx.hxx"
24 #include <osl/diagnose.h>
25 #include <rtl/instance.hxx>
26 #include <basegfx/matrix/b2dhommatrix.hxx>
27 #include <hommatrixtemplate.hxx>
28 #include <basegfx/tuple/b2dtuple.hxx>
29 #include <basegfx/vector/b2dvector.hxx>
30 #include <basegfx/matrix/b2dhommatrixtools.hxx>
31 
32 namespace basegfx
33 {
34     class Impl2DHomMatrix : public ::basegfx::internal::ImplHomMatrixTemplate< 3 >
35     {
36     };
37 
38     namespace { struct IdentityMatrix : public rtl::Static< B2DHomMatrix::ImplType,
39                                                             IdentityMatrix > {}; }
40 
B2DHomMatrix()41     B2DHomMatrix::B2DHomMatrix() :
42         mpImpl( IdentityMatrix::get() ) // use common identity matrix
43     {
44     }
45 
B2DHomMatrix(const B2DHomMatrix & rMat)46     B2DHomMatrix::B2DHomMatrix(const B2DHomMatrix& rMat) :
47         mpImpl(rMat.mpImpl)
48     {
49     }
50 
~B2DHomMatrix()51     B2DHomMatrix::~B2DHomMatrix()
52     {
53     }
54 
B2DHomMatrix(double f_0x0,double f_0x1,double f_0x2,double f_1x0,double f_1x1,double f_1x2)55     B2DHomMatrix::B2DHomMatrix(double f_0x0, double f_0x1, double f_0x2, double f_1x0, double f_1x1, double f_1x2)
56     :   mpImpl( IdentityMatrix::get() ) // use common identity matrix, will be made unique with 1st set-call
57     {
58         mpImpl->set(0, 0, f_0x0);
59         mpImpl->set(0, 1, f_0x1);
60         mpImpl->set(0, 2, f_0x2);
61         mpImpl->set(1, 0, f_1x0);
62         mpImpl->set(1, 1, f_1x1);
63         mpImpl->set(1, 2, f_1x2);
64     }
65 
operator =(const B2DHomMatrix & rMat)66     B2DHomMatrix& B2DHomMatrix::operator=(const B2DHomMatrix& rMat)
67     {
68         mpImpl = rMat.mpImpl;
69         return *this;
70     }
71 
makeUnique()72     void B2DHomMatrix::makeUnique()
73     {
74         mpImpl.make_unique();
75     }
76 
get(sal_uInt16 nRow,sal_uInt16 nColumn) const77     double B2DHomMatrix::get(sal_uInt16 nRow, sal_uInt16 nColumn) const
78     {
79         return mpImpl->get(nRow, nColumn);
80     }
81 
set(sal_uInt16 nRow,sal_uInt16 nColumn,double fValue)82     void B2DHomMatrix::set(sal_uInt16 nRow, sal_uInt16 nColumn, double fValue)
83     {
84         mpImpl->set(nRow, nColumn, fValue);
85     }
86 
set3x2(double f_0x0,double f_0x1,double f_0x2,double f_1x0,double f_1x1,double f_1x2)87     void B2DHomMatrix::set3x2(double f_0x0, double f_0x1, double f_0x2, double f_1x0, double f_1x1, double f_1x2)
88     {
89         mpImpl->set(0, 0, f_0x0);
90         mpImpl->set(0, 1, f_0x1);
91         mpImpl->set(0, 2, f_0x2);
92         mpImpl->set(1, 0, f_1x0);
93         mpImpl->set(1, 1, f_1x1);
94         mpImpl->set(1, 2, f_1x2);
95     }
96 
isLastLineDefault() const97     bool B2DHomMatrix::isLastLineDefault() const
98     {
99         return mpImpl->isLastLineDefault();
100     }
101 
isIdentity() const102     bool B2DHomMatrix::isIdentity() const
103     {
104         if(mpImpl.same_object(IdentityMatrix::get()))
105             return true;
106 
107         return mpImpl->isIdentity();
108     }
109 
identity()110     void B2DHomMatrix::identity()
111     {
112         mpImpl = IdentityMatrix::get();
113     }
114 
isInvertible() const115     bool B2DHomMatrix::isInvertible() const
116     {
117         return mpImpl->isInvertible();
118     }
119 
invert()120     bool B2DHomMatrix::invert()
121     {
122         Impl2DHomMatrix aWork(*mpImpl);
123         sal_uInt16* pIndex = new sal_uInt16[mpImpl->getEdgeLength()];
124         sal_Int16 nParity;
125 
126         if(aWork.ludcmp(pIndex, nParity))
127         {
128             mpImpl->doInvert(aWork, pIndex);
129             delete[] pIndex;
130 
131             return true;
132         }
133 
134         delete[] pIndex;
135         return false;
136     }
137 
isNormalized() const138     bool B2DHomMatrix::isNormalized() const
139     {
140         return mpImpl->isNormalized();
141     }
142 
normalize()143     void B2DHomMatrix::normalize()
144     {
145         if(!const_cast<const B2DHomMatrix*>(this)->mpImpl->isNormalized())
146             mpImpl->doNormalize();
147     }
148 
determinant() const149     double B2DHomMatrix::determinant() const
150     {
151         return mpImpl->doDeterminant();
152     }
153 
trace() const154     double B2DHomMatrix::trace() const
155     {
156         return mpImpl->doTrace();
157     }
158 
transpose()159     void B2DHomMatrix::transpose()
160     {
161         mpImpl->doTranspose();
162     }
163 
operator +=(const B2DHomMatrix & rMat)164     B2DHomMatrix& B2DHomMatrix::operator+=(const B2DHomMatrix& rMat)
165     {
166         mpImpl->doAddMatrix(*rMat.mpImpl);
167         return *this;
168     }
169 
operator -=(const B2DHomMatrix & rMat)170     B2DHomMatrix& B2DHomMatrix::operator-=(const B2DHomMatrix& rMat)
171     {
172         mpImpl->doSubMatrix(*rMat.mpImpl);
173         return *this;
174     }
175 
operator *=(double fValue)176     B2DHomMatrix& B2DHomMatrix::operator*=(double fValue)
177     {
178         const double fOne(1.0);
179 
180         if(!fTools::equal(fOne, fValue))
181             mpImpl->doMulMatrix(fValue);
182 
183         return *this;
184     }
185 
operator /=(double fValue)186     B2DHomMatrix& B2DHomMatrix::operator/=(double fValue)
187     {
188         const double fOne(1.0);
189 
190         if(!fTools::equal(fOne, fValue))
191             mpImpl->doMulMatrix(1.0 / fValue);
192 
193         return *this;
194     }
195 
operator *=(const B2DHomMatrix & rMat)196     B2DHomMatrix& B2DHomMatrix::operator*=(const B2DHomMatrix& rMat)
197     {
198         if(!rMat.isIdentity())
199             mpImpl->doMulMatrix(*rMat.mpImpl);
200 
201         return *this;
202     }
203 
operator ==(const B2DHomMatrix & rMat) const204     bool B2DHomMatrix::operator==(const B2DHomMatrix& rMat) const
205     {
206         if(mpImpl.same_object(rMat.mpImpl))
207             return true;
208 
209         return mpImpl->isEqual(*rMat.mpImpl);
210     }
211 
operator !=(const B2DHomMatrix & rMat) const212     bool B2DHomMatrix::operator!=(const B2DHomMatrix& rMat) const
213     {
214         return !(*this == rMat);
215     }
216 
rotate(double fRadiant)217     void B2DHomMatrix::rotate(double fRadiant)
218     {
219         if(!fTools::equalZero(fRadiant))
220         {
221             double fSin(0.0);
222             double fCos(1.0);
223 
224             tools::createSinCosOrthogonal(fSin, fCos, fRadiant);
225             Impl2DHomMatrix aRotMat;
226 
227             aRotMat.set(0, 0, fCos);
228             aRotMat.set(1, 1, fCos);
229             aRotMat.set(1, 0, fSin);
230             aRotMat.set(0, 1, -fSin);
231 
232             mpImpl->doMulMatrix(aRotMat);
233         }
234     }
235 
translate(double fX,double fY)236     void B2DHomMatrix::translate(double fX, double fY)
237     {
238         if(!fTools::equalZero(fX) || !fTools::equalZero(fY))
239         {
240             Impl2DHomMatrix aTransMat;
241 
242             aTransMat.set(0, 2, fX);
243             aTransMat.set(1, 2, fY);
244 
245             mpImpl->doMulMatrix(aTransMat);
246         }
247     }
248 
scale(double fX,double fY)249     void B2DHomMatrix::scale(double fX, double fY)
250     {
251         const double fOne(1.0);
252 
253         if(!fTools::equal(fOne, fX) || !fTools::equal(fOne, fY))
254         {
255             Impl2DHomMatrix aScaleMat;
256 
257             aScaleMat.set(0, 0, fX);
258             aScaleMat.set(1, 1, fY);
259 
260             mpImpl->doMulMatrix(aScaleMat);
261         }
262     }
263 
shearX(double fSx)264     void B2DHomMatrix::shearX(double fSx)
265     {
266         // #i76239# do not test against 1.0, but against 0.0. We are talking about a value not on the diagonal (!)
267         if(!fTools::equalZero(fSx))
268         {
269             Impl2DHomMatrix aShearXMat;
270 
271             aShearXMat.set(0, 1, fSx);
272 
273             mpImpl->doMulMatrix(aShearXMat);
274         }
275     }
276 
shearY(double fSy)277     void B2DHomMatrix::shearY(double fSy)
278     {
279         // #i76239# do not test against 1.0, but against 0.0. We are talking about a value not on the diagonal (!)
280         if(!fTools::equalZero(fSy))
281         {
282             Impl2DHomMatrix aShearYMat;
283 
284             aShearYMat.set(1, 0, fSy);
285 
286             mpImpl->doMulMatrix(aShearYMat);
287         }
288     }
289 
290     /** Decomposition
291 
292         New, optimized version with local shearX detection. Old version (keeping
293         below, is working well, too) used the 3D matrix decomposition when
294         shear was used. Keeping old version as comment below since it may get
295         necessary to add the determinant() test from there here, too.
296     */
decompose(B2DTuple & rScale,B2DTuple & rTranslate,double & rRotate,double & rShearX) const297     bool B2DHomMatrix::decompose(B2DTuple& rScale, B2DTuple& rTranslate, double& rRotate, double& rShearX) const
298     {
299         // when perspective is used, decompose is not made here
300         if(!mpImpl->isLastLineDefault())
301         {
302             return false;
303         }
304 
305         // reset rotate and shear and copy translation values in every case
306         rRotate = rShearX = 0.0;
307         rTranslate.setX(get(0, 2));
308         rTranslate.setY(get(1, 2));
309 
310         // test for rotation and shear
311         if(fTools::equalZero(get(0, 1)) && fTools::equalZero(get(1, 0)))
312         {
313             // no rotation and shear, copy scale values
314             rScale.setX(get(0, 0));
315             rScale.setY(get(1, 1));
316         }
317         else
318         {
319             // get the unit vectors of the transformation -> the perpendicular vectors
320             B2DVector aUnitVecX(get(0, 0), get(1, 0));
321             B2DVector aUnitVecY(get(0, 1), get(1, 1));
322             const double fScalarXY(aUnitVecX.scalar(aUnitVecY));
323 
324             // Test if shear is zero. That's the case if the unit vectors in the matrix
325             // are perpendicular -> scalar is zero. This is also the case when one of
326             // the unit vectors is zero.
327             if(fTools::equalZero(fScalarXY))
328             {
329                 // calculate unsigned scale values
330                 rScale.setX(aUnitVecX.getLength());
331                 rScale.setY(aUnitVecY.getLength());
332 
333                 // check unit vectors for zero lengths
334                 const bool bXIsZero(fTools::equalZero(rScale.getX()));
335                 const bool bYIsZero(fTools::equalZero(rScale.getY()));
336 
337                 if(bXIsZero || bYIsZero)
338                 {
339                     // still extract as much as possible. Scalings are already set
340                     if(!bXIsZero)
341                     {
342                         // get rotation of X-Axis
343                         rRotate = atan2(aUnitVecX.getY(), aUnitVecX.getX());
344                     }
345                     else if(!bYIsZero)
346                     {
347                         // get rotation of X-Axis. When assuming X and Y perpendicular
348                         // and correct rotation, it's the Y-Axis rotation minus 90 degrees
349                         rRotate = atan2(aUnitVecY.getY(), aUnitVecY.getX()) - M_PI_2;
350                     }
351 
352                     // one or both unit vectors do not exist, determinant is zero, no decomposition possible.
353                     // Eventually used rotations or shears are lost
354                     return false;
355                 }
356                 else
357                 {
358                     // no shear
359                     // calculate rotation of X unit vector relative to (1, 0)
360                     rRotate = atan2(aUnitVecX.getY(), aUnitVecX.getX());
361 
362                     // use orientation to evtl. correct sign of Y-Scale
363                     const double fCrossXY(aUnitVecX.cross(aUnitVecY));
364 
365                     if(fCrossXY < 0.0)
366                     {
367                         rScale.setY(-rScale.getY());
368                     }
369                 }
370             }
371             else
372             {
373                 // fScalarXY is not zero, thus both unit vectors exist. No need to handle that here
374                 // shear, extract it
375                 double fCrossXY(aUnitVecX.cross(aUnitVecY));
376 
377                 // get rotation by calculating angle of X unit vector relative to (1, 0).
378                 // This is before the parallel test following the motto to extract
379                 // as much as possible
380                 rRotate = atan2(aUnitVecX.getY(), aUnitVecX.getX());
381 
382                 // get unsigned scale value for X. It will not change and is useful
383                 // for further corrections
384                 rScale.setX(aUnitVecX.getLength());
385 
386                 if(fTools::equalZero(fCrossXY))
387                 {
388                     // extract as much as possible
389                     rScale.setY(aUnitVecY.getLength());
390 
391                     // unit vectors are parallel, thus not linear independent. No
392                     // useful decomposition possible. This should not happen since
393                     // the only way to get the unit vectors nearly parallel is
394                     // a very big shearing. Anyways, be prepared for hand-filled
395                     // matrices
396                     // Eventually used rotations or shears are lost
397                     return false;
398                 }
399                 else
400                 {
401                     // calculate the contained shear
402                     rShearX = fScalarXY / fCrossXY;
403 
404                     if(!fTools::equalZero(rRotate))
405                     {
406                         // To be able to correct the shear for aUnitVecY, rotation needs to be
407                         // removed first. Correction of aUnitVecX is easy, it will be rotated back to (1, 0).
408                         aUnitVecX.setX(rScale.getX());
409                         aUnitVecX.setY(0.0);
410 
411                         // for Y correction we rotate the UnitVecY back about -rRotate
412                         const double fNegRotate(-rRotate);
413                         const double fSin(sin(fNegRotate));
414                         const double fCos(cos(fNegRotate));
415 
416                         const double fNewX(aUnitVecY.getX() * fCos - aUnitVecY.getY() * fSin);
417                         const double fNewY(aUnitVecY.getX() * fSin + aUnitVecY.getY() * fCos);
418 
419                         aUnitVecY.setX(fNewX);
420                         aUnitVecY.setY(fNewY);
421                     }
422 
423                     // Correct aUnitVecY and fCrossXY to fShear=0. Rotation is already removed.
424                     // Shear correction can only work with removed rotation
425                     aUnitVecY.setX(aUnitVecY.getX() - (aUnitVecY.getY() * rShearX));
426                     fCrossXY = aUnitVecX.cross(aUnitVecY);
427 
428                     // calculate unsigned scale value for Y, after the corrections since
429                     // the shear correction WILL change the length of aUnitVecY
430                     rScale.setY(aUnitVecY.getLength());
431 
432                     // use orientation to set sign of Y-Scale
433                     if(fCrossXY < 0.0)
434                     {
435                         rScale.setY(-rScale.getY());
436                     }
437                 }
438             }
439         }
440 
441         return true;
442     }
443 } // end of namespace basegfx
444 
445 /* vim: set noet sw=4 ts=4: */
446