affinetransform.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Provides an object representation of an AffineTransform and
  16. * methods for working with it.
  17. */
  18. goog.provide('goog.math.AffineTransform');
  19. /**
  20. * Creates a 2D affine transform. An affine transform performs a linear
  21. * mapping from 2D coordinates to other 2D coordinates that preserves the
  22. * "straightness" and "parallelness" of lines.
  23. *
  24. * Such a coordinate transformation can be represented by a 3 row by 3 column
  25. * matrix with an implied last row of [ 0 0 1 ]. This matrix transforms source
  26. * coordinates (x,y) into destination coordinates (x',y') by considering them
  27. * to be a column vector and multiplying the coordinate vector by the matrix
  28. * according to the following process:
  29. * <pre>
  30. * [ x'] [ m00 m01 m02 ] [ x ] [ m00x + m01y + m02 ]
  31. * [ y'] = [ m10 m11 m12 ] [ y ] = [ m10x + m11y + m12 ]
  32. * [ 1 ] [ 0 0 1 ] [ 1 ] [ 1 ]
  33. * </pre>
  34. *
  35. * This class is optimized for speed and minimizes calculations based on its
  36. * knowledge of the underlying matrix (as opposed to say simply performing
  37. * matrix multiplication).
  38. *
  39. * @param {number=} opt_m00 The m00 coordinate of the transform.
  40. * @param {number=} opt_m10 The m10 coordinate of the transform.
  41. * @param {number=} opt_m01 The m01 coordinate of the transform.
  42. * @param {number=} opt_m11 The m11 coordinate of the transform.
  43. * @param {number=} opt_m02 The m02 coordinate of the transform.
  44. * @param {number=} opt_m12 The m12 coordinate of the transform.
  45. * @struct
  46. * @constructor
  47. * @final
  48. */
  49. goog.math.AffineTransform = function(
  50. opt_m00, opt_m10, opt_m01, opt_m11, opt_m02, opt_m12) {
  51. if (arguments.length == 6) {
  52. this.setTransform(
  53. /** @type {number} */ (opt_m00),
  54. /** @type {number} */ (opt_m10),
  55. /** @type {number} */ (opt_m01),
  56. /** @type {number} */ (opt_m11),
  57. /** @type {number} */ (opt_m02),
  58. /** @type {number} */ (opt_m12));
  59. } else if (arguments.length != 0) {
  60. throw Error('Insufficient matrix parameters');
  61. } else {
  62. this.m00_ = this.m11_ = 1;
  63. this.m10_ = this.m01_ = this.m02_ = this.m12_ = 0;
  64. }
  65. };
  66. /**
  67. * @return {boolean} Whether this transform is the identity transform.
  68. */
  69. goog.math.AffineTransform.prototype.isIdentity = function() {
  70. return this.m00_ == 1 && this.m10_ == 0 && this.m01_ == 0 && this.m11_ == 1 &&
  71. this.m02_ == 0 && this.m12_ == 0;
  72. };
  73. /**
  74. * @return {!goog.math.AffineTransform} A copy of this transform.
  75. */
  76. goog.math.AffineTransform.prototype.clone = function() {
  77. return new goog.math.AffineTransform(
  78. this.m00_, this.m10_, this.m01_, this.m11_, this.m02_, this.m12_);
  79. };
  80. /**
  81. * Sets this transform to the matrix specified by the 6 values.
  82. *
  83. * @param {number} m00 The m00 coordinate of the transform.
  84. * @param {number} m10 The m10 coordinate of the transform.
  85. * @param {number} m01 The m01 coordinate of the transform.
  86. * @param {number} m11 The m11 coordinate of the transform.
  87. * @param {number} m02 The m02 coordinate of the transform.
  88. * @param {number} m12 The m12 coordinate of the transform.
  89. * @return {!goog.math.AffineTransform} This affine transform.
  90. */
  91. goog.math.AffineTransform.prototype.setTransform = function(
  92. m00, m10, m01, m11, m02, m12) {
  93. if (!goog.isNumber(m00) || !goog.isNumber(m10) || !goog.isNumber(m01) ||
  94. !goog.isNumber(m11) || !goog.isNumber(m02) || !goog.isNumber(m12)) {
  95. throw Error('Invalid transform parameters');
  96. }
  97. this.m00_ = m00;
  98. this.m10_ = m10;
  99. this.m01_ = m01;
  100. this.m11_ = m11;
  101. this.m02_ = m02;
  102. this.m12_ = m12;
  103. return this;
  104. };
  105. /**
  106. * Sets this transform to be identical to the given transform.
  107. *
  108. * @param {!goog.math.AffineTransform} tx The transform to copy.
  109. * @return {!goog.math.AffineTransform} This affine transform.
  110. */
  111. goog.math.AffineTransform.prototype.copyFrom = function(tx) {
  112. this.m00_ = tx.m00_;
  113. this.m10_ = tx.m10_;
  114. this.m01_ = tx.m01_;
  115. this.m11_ = tx.m11_;
  116. this.m02_ = tx.m02_;
  117. this.m12_ = tx.m12_;
  118. return this;
  119. };
  120. /**
  121. * Concatenates this transform with a scaling transformation.
  122. *
  123. * @param {number} sx The x-axis scaling factor.
  124. * @param {number} sy The y-axis scaling factor.
  125. * @return {!goog.math.AffineTransform} This affine transform.
  126. */
  127. goog.math.AffineTransform.prototype.scale = function(sx, sy) {
  128. this.m00_ *= sx;
  129. this.m10_ *= sx;
  130. this.m01_ *= sy;
  131. this.m11_ *= sy;
  132. return this;
  133. };
  134. /**
  135. * Pre-concatenates this transform with a scaling transformation,
  136. * i.e. calculates the following matrix product:
  137. *
  138. * <pre>
  139. * [sx 0 0] [m00 m01 m02]
  140. * [ 0 sy 0] [m10 m11 m12]
  141. * [ 0 0 1] [ 0 0 1]
  142. * </pre>
  143. *
  144. * @param {number} sx The x-axis scaling factor.
  145. * @param {number} sy The y-axis scaling factor.
  146. * @return {!goog.math.AffineTransform} This affine transform.
  147. */
  148. goog.math.AffineTransform.prototype.preScale = function(sx, sy) {
  149. this.m00_ *= sx;
  150. this.m01_ *= sx;
  151. this.m02_ *= sx;
  152. this.m10_ *= sy;
  153. this.m11_ *= sy;
  154. this.m12_ *= sy;
  155. return this;
  156. };
  157. /**
  158. * Concatenates this transform with a translate transformation.
  159. *
  160. * @param {number} dx The distance to translate in the x direction.
  161. * @param {number} dy The distance to translate in the y direction.
  162. * @return {!goog.math.AffineTransform} This affine transform.
  163. */
  164. goog.math.AffineTransform.prototype.translate = function(dx, dy) {
  165. this.m02_ += dx * this.m00_ + dy * this.m01_;
  166. this.m12_ += dx * this.m10_ + dy * this.m11_;
  167. return this;
  168. };
  169. /**
  170. * Pre-concatenates this transform with a translate transformation,
  171. * i.e. calculates the following matrix product:
  172. *
  173. * <pre>
  174. * [1 0 dx] [m00 m01 m02]
  175. * [0 1 dy] [m10 m11 m12]
  176. * [0 0 1] [ 0 0 1]
  177. * </pre>
  178. *
  179. * @param {number} dx The distance to translate in the x direction.
  180. * @param {number} dy The distance to translate in the y direction.
  181. * @return {!goog.math.AffineTransform} This affine transform.
  182. */
  183. goog.math.AffineTransform.prototype.preTranslate = function(dx, dy) {
  184. this.m02_ += dx;
  185. this.m12_ += dy;
  186. return this;
  187. };
  188. /**
  189. * Concatenates this transform with a rotation transformation around an anchor
  190. * point.
  191. *
  192. * @param {number} theta The angle of rotation measured in radians.
  193. * @param {number} x The x coordinate of the anchor point.
  194. * @param {number} y The y coordinate of the anchor point.
  195. * @return {!goog.math.AffineTransform} This affine transform.
  196. */
  197. goog.math.AffineTransform.prototype.rotate = function(theta, x, y) {
  198. return this.concatenate(
  199. goog.math.AffineTransform.getRotateInstance(theta, x, y));
  200. };
  201. /**
  202. * Pre-concatenates this transform with a rotation transformation around an
  203. * anchor point.
  204. *
  205. * @param {number} theta The angle of rotation measured in radians.
  206. * @param {number} x The x coordinate of the anchor point.
  207. * @param {number} y The y coordinate of the anchor point.
  208. * @return {!goog.math.AffineTransform} This affine transform.
  209. */
  210. goog.math.AffineTransform.prototype.preRotate = function(theta, x, y) {
  211. return this.preConcatenate(
  212. goog.math.AffineTransform.getRotateInstance(theta, x, y));
  213. };
  214. /**
  215. * Concatenates this transform with a shear transformation.
  216. *
  217. * @param {number} shx The x shear factor.
  218. * @param {number} shy The y shear factor.
  219. * @return {!goog.math.AffineTransform} This affine transform.
  220. */
  221. goog.math.AffineTransform.prototype.shear = function(shx, shy) {
  222. var m00 = this.m00_;
  223. var m10 = this.m10_;
  224. this.m00_ += shy * this.m01_;
  225. this.m10_ += shy * this.m11_;
  226. this.m01_ += shx * m00;
  227. this.m11_ += shx * m10;
  228. return this;
  229. };
  230. /**
  231. * Pre-concatenates this transform with a shear transformation.
  232. * i.e. calculates the following matrix product:
  233. *
  234. * <pre>
  235. * [ 1 shx 0] [m00 m01 m02]
  236. * [shy 1 0] [m10 m11 m12]
  237. * [ 0 0 1] [ 0 0 1]
  238. * </pre>
  239. *
  240. * @param {number} shx The x shear factor.
  241. * @param {number} shy The y shear factor.
  242. * @return {!goog.math.AffineTransform} This affine transform.
  243. */
  244. goog.math.AffineTransform.prototype.preShear = function(shx, shy) {
  245. var m00 = this.m00_;
  246. var m01 = this.m01_;
  247. var m02 = this.m02_;
  248. this.m00_ += shx * this.m10_;
  249. this.m01_ += shx * this.m11_;
  250. this.m02_ += shx * this.m12_;
  251. this.m10_ += shy * m00;
  252. this.m11_ += shy * m01;
  253. this.m12_ += shy * m02;
  254. return this;
  255. };
  256. /**
  257. * @return {string} A string representation of this transform. The format of
  258. * of the string is compatible with SVG matrix notation, i.e.
  259. * "matrix(a,b,c,d,e,f)".
  260. * @override
  261. */
  262. goog.math.AffineTransform.prototype.toString = function() {
  263. return 'matrix(' +
  264. [this.m00_, this.m10_, this.m01_, this.m11_, this.m02_, this.m12_].join(
  265. ',') +
  266. ')';
  267. };
  268. /**
  269. * @return {number} The scaling factor in the x-direction (m00).
  270. */
  271. goog.math.AffineTransform.prototype.getScaleX = function() {
  272. return this.m00_;
  273. };
  274. /**
  275. * @return {number} The scaling factor in the y-direction (m11).
  276. */
  277. goog.math.AffineTransform.prototype.getScaleY = function() {
  278. return this.m11_;
  279. };
  280. /**
  281. * @return {number} The translation in the x-direction (m02).
  282. */
  283. goog.math.AffineTransform.prototype.getTranslateX = function() {
  284. return this.m02_;
  285. };
  286. /**
  287. * @return {number} The translation in the y-direction (m12).
  288. */
  289. goog.math.AffineTransform.prototype.getTranslateY = function() {
  290. return this.m12_;
  291. };
  292. /**
  293. * @return {number} The shear factor in the x-direction (m01).
  294. */
  295. goog.math.AffineTransform.prototype.getShearX = function() {
  296. return this.m01_;
  297. };
  298. /**
  299. * @return {number} The shear factor in the y-direction (m10).
  300. */
  301. goog.math.AffineTransform.prototype.getShearY = function() {
  302. return this.m10_;
  303. };
  304. /**
  305. * Concatenates an affine transform to this transform.
  306. *
  307. * @param {!goog.math.AffineTransform} tx The transform to concatenate.
  308. * @return {!goog.math.AffineTransform} This affine transform.
  309. */
  310. goog.math.AffineTransform.prototype.concatenate = function(tx) {
  311. var m0 = this.m00_;
  312. var m1 = this.m01_;
  313. this.m00_ = tx.m00_ * m0 + tx.m10_ * m1;
  314. this.m01_ = tx.m01_ * m0 + tx.m11_ * m1;
  315. this.m02_ += tx.m02_ * m0 + tx.m12_ * m1;
  316. m0 = this.m10_;
  317. m1 = this.m11_;
  318. this.m10_ = tx.m00_ * m0 + tx.m10_ * m1;
  319. this.m11_ = tx.m01_ * m0 + tx.m11_ * m1;
  320. this.m12_ += tx.m02_ * m0 + tx.m12_ * m1;
  321. return this;
  322. };
  323. /**
  324. * Pre-concatenates an affine transform to this transform.
  325. *
  326. * @param {!goog.math.AffineTransform} tx The transform to preconcatenate.
  327. * @return {!goog.math.AffineTransform} This affine transform.
  328. */
  329. goog.math.AffineTransform.prototype.preConcatenate = function(tx) {
  330. var m0 = this.m00_;
  331. var m1 = this.m10_;
  332. this.m00_ = tx.m00_ * m0 + tx.m01_ * m1;
  333. this.m10_ = tx.m10_ * m0 + tx.m11_ * m1;
  334. m0 = this.m01_;
  335. m1 = this.m11_;
  336. this.m01_ = tx.m00_ * m0 + tx.m01_ * m1;
  337. this.m11_ = tx.m10_ * m0 + tx.m11_ * m1;
  338. m0 = this.m02_;
  339. m1 = this.m12_;
  340. this.m02_ = tx.m00_ * m0 + tx.m01_ * m1 + tx.m02_;
  341. this.m12_ = tx.m10_ * m0 + tx.m11_ * m1 + tx.m12_;
  342. return this;
  343. };
  344. /**
  345. * Transforms an array of coordinates by this transform and stores the result
  346. * into a destination array.
  347. *
  348. * @param {!Array<number>} src The array containing the source points
  349. * as x, y value pairs.
  350. * @param {number} srcOff The offset to the first point to be transformed.
  351. * @param {!Array<number>} dst The array into which to store the transformed
  352. * point pairs.
  353. * @param {number} dstOff The offset of the location of the first transformed
  354. * point in the destination array.
  355. * @param {number} numPts The number of points to transform.
  356. */
  357. goog.math.AffineTransform.prototype.transform = function(
  358. src, srcOff, dst, dstOff, numPts) {
  359. var i = srcOff;
  360. var j = dstOff;
  361. var srcEnd = srcOff + 2 * numPts;
  362. while (i < srcEnd) {
  363. var x = src[i++];
  364. var y = src[i++];
  365. dst[j++] = x * this.m00_ + y * this.m01_ + this.m02_;
  366. dst[j++] = x * this.m10_ + y * this.m11_ + this.m12_;
  367. }
  368. };
  369. /**
  370. * @return {number} The determinant of this transform.
  371. */
  372. goog.math.AffineTransform.prototype.getDeterminant = function() {
  373. return this.m00_ * this.m11_ - this.m01_ * this.m10_;
  374. };
  375. /**
  376. * Returns whether the transform is invertible. A transform is not invertible
  377. * if the determinant is 0 or any value is non-finite or NaN.
  378. *
  379. * @return {boolean} Whether the transform is invertible.
  380. */
  381. goog.math.AffineTransform.prototype.isInvertible = function() {
  382. var det = this.getDeterminant();
  383. return isFinite(det) && isFinite(this.m02_) && isFinite(this.m12_) &&
  384. det != 0;
  385. };
  386. /**
  387. * @return {!goog.math.AffineTransform} An AffineTransform object
  388. * representing the inverse transformation.
  389. */
  390. goog.math.AffineTransform.prototype.createInverse = function() {
  391. var det = this.getDeterminant();
  392. return new goog.math.AffineTransform(
  393. this.m11_ / det, -this.m10_ / det, -this.m01_ / det, this.m00_ / det,
  394. (this.m01_ * this.m12_ - this.m11_ * this.m02_) / det,
  395. (this.m10_ * this.m02_ - this.m00_ * this.m12_) / det);
  396. };
  397. /**
  398. * Creates a transform representing a scaling transformation.
  399. *
  400. * @param {number} sx The x-axis scaling factor.
  401. * @param {number} sy The y-axis scaling factor.
  402. * @return {!goog.math.AffineTransform} A transform representing a scaling
  403. * transformation.
  404. */
  405. goog.math.AffineTransform.getScaleInstance = function(sx, sy) {
  406. return new goog.math.AffineTransform().setToScale(sx, sy);
  407. };
  408. /**
  409. * Creates a transform representing a translation transformation.
  410. *
  411. * @param {number} dx The distance to translate in the x direction.
  412. * @param {number} dy The distance to translate in the y direction.
  413. * @return {!goog.math.AffineTransform} A transform representing a
  414. * translation transformation.
  415. */
  416. goog.math.AffineTransform.getTranslateInstance = function(dx, dy) {
  417. return new goog.math.AffineTransform().setToTranslation(dx, dy);
  418. };
  419. /**
  420. * Creates a transform representing a shearing transformation.
  421. *
  422. * @param {number} shx The x-axis shear factor.
  423. * @param {number} shy The y-axis shear factor.
  424. * @return {!goog.math.AffineTransform} A transform representing a shearing
  425. * transformation.
  426. */
  427. goog.math.AffineTransform.getShearInstance = function(shx, shy) {
  428. return new goog.math.AffineTransform().setToShear(shx, shy);
  429. };
  430. /**
  431. * Creates a transform representing a rotation transformation.
  432. *
  433. * @param {number} theta The angle of rotation measured in radians.
  434. * @param {number} x The x coordinate of the anchor point.
  435. * @param {number} y The y coordinate of the anchor point.
  436. * @return {!goog.math.AffineTransform} A transform representing a rotation
  437. * transformation.
  438. */
  439. goog.math.AffineTransform.getRotateInstance = function(theta, x, y) {
  440. return new goog.math.AffineTransform().setToRotation(theta, x, y);
  441. };
  442. /**
  443. * Sets this transform to a scaling transformation.
  444. *
  445. * @param {number} sx The x-axis scaling factor.
  446. * @param {number} sy The y-axis scaling factor.
  447. * @return {!goog.math.AffineTransform} This affine transform.
  448. */
  449. goog.math.AffineTransform.prototype.setToScale = function(sx, sy) {
  450. return this.setTransform(sx, 0, 0, sy, 0, 0);
  451. };
  452. /**
  453. * Sets this transform to a translation transformation.
  454. *
  455. * @param {number} dx The distance to translate in the x direction.
  456. * @param {number} dy The distance to translate in the y direction.
  457. * @return {!goog.math.AffineTransform} This affine transform.
  458. */
  459. goog.math.AffineTransform.prototype.setToTranslation = function(dx, dy) {
  460. return this.setTransform(1, 0, 0, 1, dx, dy);
  461. };
  462. /**
  463. * Sets this transform to a shearing transformation.
  464. *
  465. * @param {number} shx The x-axis shear factor.
  466. * @param {number} shy The y-axis shear factor.
  467. * @return {!goog.math.AffineTransform} This affine transform.
  468. */
  469. goog.math.AffineTransform.prototype.setToShear = function(shx, shy) {
  470. return this.setTransform(1, shy, shx, 1, 0, 0);
  471. };
  472. /**
  473. * Sets this transform to a rotation transformation.
  474. *
  475. * @param {number} theta The angle of rotation measured in radians.
  476. * @param {number} x The x coordinate of the anchor point.
  477. * @param {number} y The y coordinate of the anchor point.
  478. * @return {!goog.math.AffineTransform} This affine transform.
  479. */
  480. goog.math.AffineTransform.prototype.setToRotation = function(theta, x, y) {
  481. var cos = Math.cos(theta);
  482. var sin = Math.sin(theta);
  483. return this.setTransform(
  484. cos, sin, -sin, cos, x - x * cos + y * sin, y - x * sin - y * cos);
  485. };
  486. /**
  487. * Compares two affine transforms for equality.
  488. *
  489. * @param {goog.math.AffineTransform} tx The other affine transform.
  490. * @return {boolean} whether the two transforms are equal.
  491. */
  492. goog.math.AffineTransform.prototype.equals = function(tx) {
  493. if (this == tx) {
  494. return true;
  495. }
  496. if (!tx) {
  497. return false;
  498. }
  499. return this.m00_ == tx.m00_ && this.m01_ == tx.m01_ && this.m02_ == tx.m02_ &&
  500. this.m10_ == tx.m10_ && this.m11_ == tx.m11_ && this.m12_ == tx.m12_;
  501. };