canvaselement.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. // Copyright 2007 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 Objects representing shapes drawn on a canvas.
  16. * @author robbyw@google.com (Robby Walker)
  17. */
  18. goog.provide('goog.graphics.CanvasEllipseElement');
  19. goog.provide('goog.graphics.CanvasGroupElement');
  20. goog.provide('goog.graphics.CanvasImageElement');
  21. goog.provide('goog.graphics.CanvasPathElement');
  22. goog.provide('goog.graphics.CanvasRectElement');
  23. goog.provide('goog.graphics.CanvasTextElement');
  24. goog.require('goog.array');
  25. goog.require('goog.dom');
  26. goog.require('goog.dom.TagName');
  27. goog.require('goog.dom.safe');
  28. goog.require('goog.graphics.EllipseElement');
  29. goog.require('goog.graphics.Font');
  30. goog.require('goog.graphics.GroupElement');
  31. goog.require('goog.graphics.ImageElement');
  32. goog.require('goog.graphics.Path');
  33. goog.require('goog.graphics.PathElement');
  34. goog.require('goog.graphics.RectElement');
  35. goog.require('goog.graphics.TextElement');
  36. goog.require('goog.html.SafeHtml');
  37. goog.require('goog.html.uncheckedconversions');
  38. goog.require('goog.math');
  39. goog.require('goog.string');
  40. goog.require('goog.string.Const');
  41. /**
  42. * Object representing a group of objects in a canvas.
  43. * This is an implementation of the goog.graphics.GroupElement interface.
  44. * You should not construct objects from this constructor. The graphics
  45. * will return the object for you.
  46. * @param {goog.graphics.CanvasGraphics} graphics The graphics creating
  47. * this element.
  48. * @constructor
  49. * @extends {goog.graphics.GroupElement}
  50. * @deprecated goog.graphics is deprecated. It existed to abstract over browser
  51. * differences before the canvas tag was widely supported. See
  52. * http://en.wikipedia.org/wiki/Canvas_element for details.
  53. * @final
  54. */
  55. goog.graphics.CanvasGroupElement = function(graphics) {
  56. goog.graphics.GroupElement.call(this, null, graphics);
  57. /**
  58. * Children contained by this group.
  59. * @type {Array<goog.graphics.Element>}
  60. * @private
  61. */
  62. this.children_ = [];
  63. };
  64. goog.inherits(goog.graphics.CanvasGroupElement, goog.graphics.GroupElement);
  65. /**
  66. * Remove all drawing elements from the group.
  67. * @override
  68. */
  69. goog.graphics.CanvasGroupElement.prototype.clear = function() {
  70. if (this.children_.length) {
  71. this.children_.length = 0;
  72. this.getGraphics().redraw();
  73. }
  74. };
  75. /**
  76. * Set the size of the group element.
  77. * @param {number|string} width The width of the group element.
  78. * @param {number|string} height The height of the group element.
  79. * @override
  80. */
  81. goog.graphics.CanvasGroupElement.prototype.setSize = function(width, height) {
  82. // Do nothing.
  83. };
  84. /**
  85. * Append a child to the group. Does not draw it
  86. * @param {goog.graphics.Element} element The child to append.
  87. */
  88. goog.graphics.CanvasGroupElement.prototype.appendChild = function(element) {
  89. this.children_.push(element);
  90. };
  91. /**
  92. * Draw the group.
  93. * @param {CanvasRenderingContext2D} ctx The context to draw the element in.
  94. */
  95. goog.graphics.CanvasGroupElement.prototype.draw = function(ctx) {
  96. for (var i = 0, len = this.children_.length; i < len; i++) {
  97. this.getGraphics().drawElement(this.children_[i]);
  98. }
  99. };
  100. /**
  101. * Removes an element from the group.
  102. * @param {!goog.graphics.Element} elem the element to remove.
  103. */
  104. goog.graphics.CanvasGroupElement.prototype.removeElement = function(elem) {
  105. goog.array.removeIf(this.children_, function(child) {
  106. // If the child has children (and thus is a group element)
  107. // call removeElement on that group
  108. if (child.children_) {
  109. child.removeElement(elem);
  110. return false;
  111. } else {
  112. return child === elem;
  113. }
  114. });
  115. };
  116. /**
  117. * Thin wrapper for canvas ellipse elements.
  118. * This is an implementation of the goog.graphics.EllipseElement interface.
  119. * You should not construct objects from this constructor. The graphics
  120. * will return the object for you.
  121. * @param {Element} element The DOM element to wrap.
  122. * @param {goog.graphics.CanvasGraphics} graphics The graphics creating
  123. * this element.
  124. * @param {number} cx Center X coordinate.
  125. * @param {number} cy Center Y coordinate.
  126. * @param {number} rx Radius length for the x-axis.
  127. * @param {number} ry Radius length for the y-axis.
  128. * @param {goog.graphics.Stroke} stroke The stroke to use for this element.
  129. * @param {goog.graphics.Fill} fill The fill to use for this element.
  130. * @constructor
  131. * @extends {goog.graphics.EllipseElement}
  132. * @final
  133. */
  134. goog.graphics.CanvasEllipseElement = function(
  135. element, graphics, cx, cy, rx, ry, stroke, fill) {
  136. goog.graphics.EllipseElement.call(this, element, graphics, stroke, fill);
  137. /**
  138. * X coordinate of the ellipse center.
  139. * @type {number}
  140. * @private
  141. */
  142. this.cx_ = cx;
  143. /**
  144. * Y coordinate of the ellipse center.
  145. * @type {number}
  146. * @private
  147. */
  148. this.cy_ = cy;
  149. /**
  150. * Radius length for the x-axis.
  151. * @type {number}
  152. * @private
  153. */
  154. this.rx_ = rx;
  155. /**
  156. * Radius length for the y-axis.
  157. * @type {number}
  158. * @private
  159. */
  160. this.ry_ = ry;
  161. /**
  162. * Internal path approximating an ellipse.
  163. * @type {goog.graphics.Path}
  164. * @private
  165. */
  166. this.path_ = new goog.graphics.Path();
  167. this.setUpPath_();
  168. /**
  169. * Internal path element that actually does the drawing.
  170. * @type {goog.graphics.CanvasPathElement}
  171. * @private
  172. */
  173. this.pathElement_ = new goog.graphics.CanvasPathElement(
  174. null, graphics, this.path_, stroke, fill);
  175. };
  176. goog.inherits(goog.graphics.CanvasEllipseElement, goog.graphics.EllipseElement);
  177. /**
  178. * Sets up the path.
  179. * @private
  180. */
  181. goog.graphics.CanvasEllipseElement.prototype.setUpPath_ = function() {
  182. this.path_.clear();
  183. this.path_.moveTo(
  184. this.cx_ + goog.math.angleDx(0, this.rx_),
  185. this.cy_ + goog.math.angleDy(0, this.ry_));
  186. this.path_.arcTo(this.rx_, this.ry_, 0, 360);
  187. this.path_.close();
  188. };
  189. /**
  190. * Update the center point of the ellipse.
  191. * @param {number} cx Center X coordinate.
  192. * @param {number} cy Center Y coordinate.
  193. * @override
  194. */
  195. goog.graphics.CanvasEllipseElement.prototype.setCenter = function(cx, cy) {
  196. this.cx_ = cx;
  197. this.cy_ = cy;
  198. this.setUpPath_();
  199. this.pathElement_.setPath(/** @type {!goog.graphics.Path} */ (this.path_));
  200. };
  201. /**
  202. * Update the radius of the ellipse.
  203. * @param {number} rx Center X coordinate.
  204. * @param {number} ry Center Y coordinate.
  205. * @override
  206. */
  207. goog.graphics.CanvasEllipseElement.prototype.setRadius = function(rx, ry) {
  208. this.rx_ = rx;
  209. this.ry_ = ry;
  210. this.setUpPath_();
  211. this.pathElement_.setPath(/** @type {!goog.graphics.Path} */ (this.path_));
  212. };
  213. /**
  214. * Draw the ellipse. Should be treated as package scope.
  215. * @param {CanvasRenderingContext2D} ctx The context to draw the element in.
  216. */
  217. goog.graphics.CanvasEllipseElement.prototype.draw = function(ctx) {
  218. this.pathElement_.draw(ctx);
  219. };
  220. /**
  221. * Thin wrapper for canvas rectangle elements.
  222. * This is an implementation of the goog.graphics.RectElement interface.
  223. * You should not construct objects from this constructor. The graphics
  224. * will return the object for you.
  225. * @param {Element} element The DOM element to wrap.
  226. * @param {goog.graphics.CanvasGraphics} graphics The graphics creating
  227. * this element.
  228. * @param {number} x X coordinate (left).
  229. * @param {number} y Y coordinate (top).
  230. * @param {number} w Width of rectangle.
  231. * @param {number} h Height of rectangle.
  232. * @param {goog.graphics.Stroke} stroke The stroke to use for this element.
  233. * @param {goog.graphics.Fill} fill The fill to use for this element.
  234. * @constructor
  235. * @extends {goog.graphics.RectElement}
  236. * @final
  237. */
  238. goog.graphics.CanvasRectElement = function(
  239. element, graphics, x, y, w, h, stroke, fill) {
  240. goog.graphics.RectElement.call(this, element, graphics, stroke, fill);
  241. /**
  242. * X coordinate of the top left corner.
  243. * @type {number}
  244. * @private
  245. */
  246. this.x_ = x;
  247. /**
  248. * Y coordinate of the top left corner.
  249. * @type {number}
  250. * @private
  251. */
  252. this.y_ = y;
  253. /**
  254. * Width of the rectangle.
  255. * @type {number}
  256. * @private
  257. */
  258. this.w_ = w;
  259. /**
  260. * Height of the rectangle.
  261. * @type {number}
  262. * @private
  263. */
  264. this.h_ = h;
  265. };
  266. goog.inherits(goog.graphics.CanvasRectElement, goog.graphics.RectElement);
  267. /**
  268. * Update the position of the rectangle.
  269. * @param {number} x X coordinate (left).
  270. * @param {number} y Y coordinate (top).
  271. * @override
  272. */
  273. goog.graphics.CanvasRectElement.prototype.setPosition = function(x, y) {
  274. this.x_ = x;
  275. this.y_ = y;
  276. if (this.drawn_) {
  277. this.getGraphics().redraw();
  278. }
  279. };
  280. /**
  281. * Whether the rectangle has been drawn yet.
  282. * @type {boolean}
  283. * @private
  284. */
  285. goog.graphics.CanvasRectElement.prototype.drawn_ = false;
  286. /**
  287. * Update the size of the rectangle.
  288. * @param {number} width Width of rectangle.
  289. * @param {number} height Height of rectangle.
  290. * @override
  291. */
  292. goog.graphics.CanvasRectElement.prototype.setSize = function(width, height) {
  293. this.w_ = width;
  294. this.h_ = height;
  295. if (this.drawn_) {
  296. this.getGraphics().redraw();
  297. }
  298. };
  299. /**
  300. * Draw the rectangle. Should be treated as package scope.
  301. * @param {CanvasRenderingContext2D} ctx The context to draw the element in.
  302. */
  303. goog.graphics.CanvasRectElement.prototype.draw = function(ctx) {
  304. this.drawn_ = true;
  305. ctx.beginPath();
  306. ctx.moveTo(this.x_, this.y_);
  307. ctx.lineTo(this.x_, this.y_ + this.h_);
  308. ctx.lineTo(this.x_ + this.w_, this.y_ + this.h_);
  309. ctx.lineTo(this.x_ + this.w_, this.y_);
  310. ctx.closePath();
  311. };
  312. /**
  313. * Thin wrapper for canvas path elements.
  314. * This is an implementation of the goog.graphics.PathElement interface.
  315. * You should not construct objects from this constructor. The graphics
  316. * will return the object for you.
  317. * @param {Element} element The DOM element to wrap.
  318. * @param {goog.graphics.CanvasGraphics} graphics The graphics creating
  319. * this element.
  320. * @param {!goog.graphics.Path} path The path object to draw.
  321. * @param {goog.graphics.Stroke} stroke The stroke to use for this element.
  322. * @param {goog.graphics.Fill} fill The fill to use for this element.
  323. * @constructor
  324. * @extends {goog.graphics.PathElement}
  325. * @final
  326. */
  327. goog.graphics.CanvasPathElement = function(
  328. element, graphics, path, stroke, fill) {
  329. goog.graphics.PathElement.call(this, element, graphics, stroke, fill);
  330. this.setPath(path);
  331. };
  332. goog.inherits(goog.graphics.CanvasPathElement, goog.graphics.PathElement);
  333. /**
  334. * Whether the shape has been drawn yet.
  335. * @type {boolean}
  336. * @private
  337. */
  338. goog.graphics.CanvasPathElement.prototype.drawn_ = false;
  339. /**
  340. * The path to draw.
  341. * @type {goog.graphics.Path}
  342. * @private
  343. */
  344. goog.graphics.CanvasPathElement.prototype.path_;
  345. /**
  346. * Update the underlying path.
  347. * @param {!goog.graphics.Path} path The path object to draw.
  348. * @override
  349. */
  350. goog.graphics.CanvasPathElement.prototype.setPath = function(path) {
  351. this.path_ =
  352. path.isSimple() ? path : goog.graphics.Path.createSimplifiedPath(path);
  353. if (this.drawn_) {
  354. this.getGraphics().redraw();
  355. }
  356. };
  357. /**
  358. * Draw the path. Should be treated as package scope.
  359. * @param {CanvasRenderingContext2D} ctx The context to draw the element in.
  360. * @suppress {deprecated} goog.graphics is deprecated.
  361. */
  362. goog.graphics.CanvasPathElement.prototype.draw = function(ctx) {
  363. this.drawn_ = true;
  364. ctx.beginPath();
  365. this.path_.forEachSegment(function(segment, args) {
  366. switch (segment) {
  367. case goog.graphics.Path.Segment.MOVETO:
  368. ctx.moveTo(args[0], args[1]);
  369. break;
  370. case goog.graphics.Path.Segment.LINETO:
  371. for (var i = 0; i < args.length; i += 2) {
  372. ctx.lineTo(args[i], args[i + 1]);
  373. }
  374. break;
  375. case goog.graphics.Path.Segment.CURVETO:
  376. for (var i = 0; i < args.length; i += 6) {
  377. ctx.bezierCurveTo(
  378. args[i], args[i + 1], args[i + 2], args[i + 3], args[i + 4],
  379. args[i + 5]);
  380. }
  381. break;
  382. case goog.graphics.Path.Segment.ARCTO:
  383. throw Error('Canvas paths cannot contain arcs');
  384. case goog.graphics.Path.Segment.CLOSE:
  385. ctx.closePath();
  386. break;
  387. }
  388. });
  389. };
  390. /**
  391. * Thin wrapper for canvas text elements.
  392. * This is an implementation of the goog.graphics.TextElement interface.
  393. * You should not construct objects from this constructor. The graphics
  394. * will return the object for you.
  395. * @param {!goog.graphics.CanvasGraphics} graphics The graphics creating
  396. * this element.
  397. * @param {string} text The text to draw.
  398. * @param {number} x1 X coordinate of start of line.
  399. * @param {number} y1 Y coordinate of start of line.
  400. * @param {number} x2 X coordinate of end of line.
  401. * @param {number} y2 Y coordinate of end of line.
  402. * @param {?string} align Horizontal alignment: left (default), center, right.
  403. * @param {!goog.graphics.Font} font Font describing the font properties.
  404. * @param {goog.graphics.Stroke} stroke The stroke to use for this element.
  405. * @param {goog.graphics.Fill} fill The fill to use for this element.
  406. * @constructor
  407. * @extends {goog.graphics.TextElement}
  408. * @final
  409. */
  410. goog.graphics.CanvasTextElement = function(
  411. graphics, text, x1, y1, x2, y2, align, font, stroke, fill) {
  412. var element = goog.dom.createDom(
  413. goog.dom.TagName.DIV,
  414. {'style': 'display:table;position:absolute;padding:0;margin:0;border:0'});
  415. goog.graphics.TextElement.call(this, element, graphics, stroke, fill);
  416. /**
  417. * The text to draw.
  418. * @type {string}
  419. * @private
  420. */
  421. this.text_ = text;
  422. /**
  423. * X coordinate of the start of the line the text is drawn on.
  424. * @type {number}
  425. * @private
  426. */
  427. this.x1_ = x1;
  428. /**
  429. * Y coordinate of the start of the line the text is drawn on.
  430. * @type {number}
  431. * @private
  432. */
  433. this.y1_ = y1;
  434. /**
  435. * X coordinate of the end of the line the text is drawn on.
  436. * @type {number}
  437. * @private
  438. */
  439. this.x2_ = x2;
  440. /**
  441. * Y coordinate of the end of the line the text is drawn on.
  442. * @type {number}
  443. * @private
  444. */
  445. this.y2_ = y2;
  446. /**
  447. * Horizontal alignment: left (default), center, right.
  448. * @type {string}
  449. * @private
  450. */
  451. this.align_ = align || 'left';
  452. /**
  453. * Font object describing the font properties.
  454. * @type {goog.graphics.Font}
  455. * @private
  456. */
  457. this.font_ = font;
  458. /**
  459. * The inner element that contains the text.
  460. * @type {Element}
  461. * @private
  462. */
  463. this.innerElement_ = goog.dom.createDom(
  464. goog.dom.TagName.DIV,
  465. {'style': 'display:table-cell;padding: 0;margin: 0;border: 0'});
  466. this.updateStyle_();
  467. this.updateText_();
  468. // Append to the DOM.
  469. graphics.getElement().appendChild(element);
  470. element.appendChild(this.innerElement_);
  471. };
  472. goog.inherits(goog.graphics.CanvasTextElement, goog.graphics.TextElement);
  473. /**
  474. * Update the displayed text of the element.
  475. * @param {string} text The text to draw.
  476. * @override
  477. */
  478. goog.graphics.CanvasTextElement.prototype.setText = function(text) {
  479. this.text_ = text;
  480. this.updateText_();
  481. };
  482. /**
  483. * Sets the fill for this element.
  484. * @param {goog.graphics.Fill} fill The fill object.
  485. * @override
  486. */
  487. goog.graphics.CanvasTextElement.prototype.setFill = function(fill) {
  488. this.fill = fill;
  489. var element = this.getElement();
  490. if (element) {
  491. element.style.color = fill.getColor() || fill.getColor1();
  492. }
  493. };
  494. /**
  495. * Sets the stroke for this element.
  496. * @param {goog.graphics.Stroke} stroke The stroke object.
  497. * @override
  498. */
  499. goog.graphics.CanvasTextElement.prototype.setStroke = function(stroke) {
  500. // Ignore stroke
  501. };
  502. /**
  503. * Draw the text. Should be treated as package scope.
  504. * @param {CanvasRenderingContext2D} ctx The context to draw the element in.
  505. */
  506. goog.graphics.CanvasTextElement.prototype.draw = function(ctx) {
  507. // Do nothing - the text is already drawn.
  508. };
  509. /**
  510. * Update the styles of the DIVs.
  511. * @private
  512. */
  513. goog.graphics.CanvasTextElement.prototype.updateStyle_ = function() {
  514. var x1 = this.x1_;
  515. var x2 = this.x2_;
  516. var y1 = this.y1_;
  517. var y2 = this.y2_;
  518. var align = this.align_;
  519. var font = this.font_;
  520. var style = this.getElement().style;
  521. var scaleX = this.getGraphics().getPixelScaleX();
  522. var scaleY = this.getGraphics().getPixelScaleY();
  523. if (x1 == x2) {
  524. // Special case vertical text
  525. style.lineHeight = '90%';
  526. this.innerElement_.style.verticalAlign =
  527. align == 'center' ? 'middle' : align == 'left' ?
  528. (y1 < y2 ? 'top' : 'bottom') :
  529. y1 < y2 ? 'bottom' : 'top';
  530. style.textAlign = 'center';
  531. var w = font.size * scaleX;
  532. style.top = Math.round(Math.min(y1, y2) * scaleY) + 'px';
  533. style.left = Math.round((x1 - w / 2) * scaleX) + 'px';
  534. style.width = Math.round(w) + 'px';
  535. style.height = Math.abs(y1 - y2) * scaleY + 'px';
  536. style.fontSize = font.size * 0.6 * scaleY + 'pt';
  537. } else {
  538. style.lineHeight = '100%';
  539. this.innerElement_.style.verticalAlign = 'top';
  540. style.textAlign = align;
  541. style.top = Math.round(((y1 + y2) / 2 - font.size * 2 / 3) * scaleY) + 'px';
  542. style.left = Math.round(x1 * scaleX) + 'px';
  543. style.width = Math.round(Math.abs(x2 - x1) * scaleX) + 'px';
  544. style.height = 'auto';
  545. style.fontSize = font.size * scaleY + 'pt';
  546. }
  547. style.fontWeight = font.bold ? 'bold' : 'normal';
  548. style.fontStyle = font.italic ? 'italic' : 'normal';
  549. style.fontFamily = font.family;
  550. var fill = this.getFill();
  551. style.color = fill.getColor() || fill.getColor1();
  552. };
  553. /**
  554. * Update the text content.
  555. * @private
  556. */
  557. goog.graphics.CanvasTextElement.prototype.updateText_ = function() {
  558. if (this.x1_ == this.x2_) {
  559. // Special case vertical text
  560. var html =
  561. goog.array
  562. .map(
  563. this.text_.split(''),
  564. function(entry) { return goog.string.htmlEscape(entry); })
  565. .join('<br>');
  566. // Creating a SafeHtml for each character would be quite expensive, and it's
  567. // obvious that this is safe, so an unchecked conversion is appropriate.
  568. var safeHtml =
  569. goog.html.uncheckedconversions
  570. .safeHtmlFromStringKnownToSatisfyTypeContract(
  571. goog.string.Const.from('Concatenate escaped chars and <br>'),
  572. html);
  573. goog.dom.safe.setInnerHtml(
  574. /** @type {!Element} */ (this.innerElement_), safeHtml);
  575. } else {
  576. goog.dom.safe.setInnerHtml(
  577. /** @type {!Element} */ (this.innerElement_),
  578. goog.html.SafeHtml.htmlEscape(this.text_));
  579. }
  580. };
  581. /**
  582. * Thin wrapper for canvas image elements.
  583. * This is an implementation of the goog.graphics.ImageElement interface.
  584. * You should not construct objects from this constructor. The graphics
  585. * will return the object for you.
  586. * @param {Element} element The DOM element to wrap.
  587. * @param {goog.graphics.CanvasGraphics} graphics The graphics creating
  588. * this element.
  589. * @param {number} x X coordinate (left).
  590. * @param {number} y Y coordinate (top).
  591. * @param {number} w Width of rectangle.
  592. * @param {number} h Height of rectangle.
  593. * @param {string} src Source of the image.
  594. * @constructor
  595. * @extends {goog.graphics.ImageElement}
  596. * @final
  597. */
  598. goog.graphics.CanvasImageElement = function(
  599. element, graphics, x, y, w, h, src) {
  600. goog.graphics.ImageElement.call(this, element, graphics);
  601. /**
  602. * X coordinate of the top left corner.
  603. * @type {number}
  604. * @private
  605. */
  606. this.x_ = x;
  607. /**
  608. * Y coordinate of the top left corner.
  609. * @type {number}
  610. * @private
  611. */
  612. this.y_ = y;
  613. /**
  614. * Width of the rectangle.
  615. * @type {number}
  616. * @private
  617. */
  618. this.w_ = w;
  619. /**
  620. * Height of the rectangle.
  621. * @type {number}
  622. * @private
  623. */
  624. this.h_ = h;
  625. /**
  626. * URL of the image source.
  627. * @type {string}
  628. * @private
  629. */
  630. this.src_ = src;
  631. };
  632. goog.inherits(goog.graphics.CanvasImageElement, goog.graphics.ImageElement);
  633. /**
  634. * Whether the image has been drawn yet.
  635. * @type {boolean}
  636. * @private
  637. */
  638. goog.graphics.CanvasImageElement.prototype.drawn_ = false;
  639. /**
  640. * Update the position of the image.
  641. * @param {number} x X coordinate (left).
  642. * @param {number} y Y coordinate (top).
  643. * @override
  644. */
  645. goog.graphics.CanvasImageElement.prototype.setPosition = function(x, y) {
  646. this.x_ = x;
  647. this.y_ = y;
  648. if (this.drawn_) {
  649. this.getGraphics().redraw();
  650. }
  651. };
  652. /**
  653. * Update the size of the image.
  654. * @param {number} width Width of rectangle.
  655. * @param {number} height Height of rectangle.
  656. * @override
  657. */
  658. goog.graphics.CanvasImageElement.prototype.setSize = function(width, height) {
  659. this.w_ = width;
  660. this.h_ = height;
  661. if (this.drawn_) {
  662. this.getGraphics().redraw();
  663. }
  664. };
  665. /**
  666. * Update the source of the image.
  667. * @param {string} src Source of the image.
  668. * @override
  669. */
  670. goog.graphics.CanvasImageElement.prototype.setSource = function(src) {
  671. this.src_ = src;
  672. if (this.drawn_) {
  673. // TODO(robbyw): Probably need to reload the image here.
  674. this.getGraphics().redraw();
  675. }
  676. };
  677. /**
  678. * Draw the image. Should be treated as package scope.
  679. * @param {CanvasRenderingContext2D} ctx The context to draw the element in.
  680. */
  681. goog.graphics.CanvasImageElement.prototype.draw = function(ctx) {
  682. if (this.img_) {
  683. if (this.w_ && this.h_) {
  684. // If the image is already loaded, draw it.
  685. ctx.drawImage(this.img_, this.x_, this.y_, this.w_, this.h_);
  686. }
  687. this.drawn_ = true;
  688. } else {
  689. // Otherwise, load it.
  690. var img = new Image();
  691. img.onload = goog.bind(this.handleImageLoad_, this, img);
  692. // TODO(robbyw): Handle image load errors.
  693. img.src = this.src_;
  694. }
  695. };
  696. /**
  697. * Handle an image load.
  698. * @param {Element} img The image element that finished loading.
  699. * @private
  700. */
  701. goog.graphics.CanvasImageElement.prototype.handleImageLoad_ = function(img) {
  702. this.img_ = img;
  703. // TODO(robbyw): Add a small delay to catch batched images
  704. this.getGraphics().redraw();
  705. };