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