affinetransform.js 17 KB

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