Source: main/webapp/modules/Layer.js

  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. var Guacamole = Guacamole || {};
  20. /**
  21. * Abstract ordered drawing surface. Each Layer contains a canvas element and
  22. * provides simple drawing instructions for drawing to that canvas element,
  23. * however unlike the canvas element itself, drawing operations on a Layer are
  24. * guaranteed to run in order, even if such an operation must wait for an image
  25. * to load before completing.
  26. *
  27. * @constructor
  28. *
  29. * @param {!number} width
  30. * The width of the Layer, in pixels. The canvas element backing this Layer
  31. * will be given this width.
  32. *
  33. * @param {!number} height
  34. * The height of the Layer, in pixels. The canvas element backing this
  35. * Layer will be given this height.
  36. */
  37. Guacamole.Layer = function(width, height) {
  38. /**
  39. * Reference to this Layer.
  40. *
  41. * @private
  42. * @type {!Guacamole.Layer}
  43. */
  44. var layer = this;
  45. /**
  46. * The number of pixels the width or height of a layer must change before
  47. * the underlying canvas is resized. The underlying canvas will be kept at
  48. * dimensions which are integer multiples of this factor.
  49. *
  50. * @private
  51. * @constant
  52. * @type {!number}
  53. */
  54. var CANVAS_SIZE_FACTOR = 64;
  55. /**
  56. * The canvas element backing this Layer.
  57. *
  58. * @private
  59. * @type {!HTMLCanvasElement}
  60. */
  61. var canvas = document.createElement("canvas");
  62. /**
  63. * The 2D display context of the canvas element backing this Layer.
  64. *
  65. * @private
  66. * @type {!CanvasRenderingContext2D}
  67. */
  68. var context = canvas.getContext("2d");
  69. context.save();
  70. /**
  71. * Whether the layer has not yet been drawn to. Once any draw operation
  72. * which affects the underlying canvas is invoked, this flag will be set to
  73. * false.
  74. *
  75. * @private
  76. * @type {!boolean}
  77. */
  78. var empty = true;
  79. /**
  80. * Whether a new path should be started with the next path drawing
  81. * operations.
  82. *
  83. * @private
  84. * @type {!boolean}
  85. */
  86. var pathClosed = true;
  87. /**
  88. * The number of states on the state stack.
  89. *
  90. * Note that there will ALWAYS be one element on the stack, but that
  91. * element is not exposed. It is only used to reset the layer to its
  92. * initial state.
  93. *
  94. * @private
  95. * @type {!number}
  96. */
  97. var stackSize = 0;
  98. /**
  99. * Map of all Guacamole channel masks to HTML5 canvas composite operation
  100. * names. Not all channel mask combinations are currently implemented.
  101. *
  102. * @private
  103. * @type {!Object.<number, string>}
  104. */
  105. var compositeOperation = {
  106. /* 0x0 NOT IMPLEMENTED */
  107. 0x1: "destination-in",
  108. 0x2: "destination-out",
  109. /* 0x3 NOT IMPLEMENTED */
  110. 0x4: "source-in",
  111. /* 0x5 NOT IMPLEMENTED */
  112. 0x6: "source-atop",
  113. /* 0x7 NOT IMPLEMENTED */
  114. 0x8: "source-out",
  115. 0x9: "destination-atop",
  116. 0xA: "xor",
  117. 0xB: "destination-over",
  118. 0xC: "copy",
  119. /* 0xD NOT IMPLEMENTED */
  120. 0xE: "source-over",
  121. 0xF: "lighter"
  122. };
  123. /**
  124. * Resizes the canvas element backing this Layer. This function should only
  125. * be used internally.
  126. *
  127. * @private
  128. * @param {number} [newWidth=0]
  129. * The new width to assign to this Layer.
  130. *
  131. * @param {number} [newHeight=0]
  132. * The new height to assign to this Layer.
  133. */
  134. var resize = function resize(newWidth, newHeight) {
  135. // Default size to zero
  136. newWidth = newWidth || 0;
  137. newHeight = newHeight || 0;
  138. // Calculate new dimensions of internal canvas
  139. var canvasWidth = Math.ceil(newWidth / CANVAS_SIZE_FACTOR) * CANVAS_SIZE_FACTOR;
  140. var canvasHeight = Math.ceil(newHeight / CANVAS_SIZE_FACTOR) * CANVAS_SIZE_FACTOR;
  141. // Resize only if canvas dimensions are actually changing
  142. if (canvas.width !== canvasWidth || canvas.height !== canvasHeight) {
  143. // Copy old data only if relevant and non-empty
  144. var oldData = null;
  145. if (!empty && canvas.width !== 0 && canvas.height !== 0) {
  146. // Create canvas and context for holding old data
  147. oldData = document.createElement("canvas");
  148. oldData.width = Math.min(layer.width, newWidth);
  149. oldData.height = Math.min(layer.height, newHeight);
  150. var oldDataContext = oldData.getContext("2d");
  151. // Copy image data from current
  152. oldDataContext.drawImage(canvas,
  153. 0, 0, oldData.width, oldData.height,
  154. 0, 0, oldData.width, oldData.height);
  155. }
  156. // Preserve composite operation
  157. var oldCompositeOperation = context.globalCompositeOperation;
  158. // Resize canvas
  159. canvas.width = canvasWidth;
  160. canvas.height = canvasHeight;
  161. // Redraw old data, if any
  162. if (oldData)
  163. context.drawImage(oldData,
  164. 0, 0, oldData.width, oldData.height,
  165. 0, 0, oldData.width, oldData.height);
  166. // Restore composite operation
  167. context.globalCompositeOperation = oldCompositeOperation;
  168. // Acknowledge reset of stack (happens on resize of canvas)
  169. stackSize = 0;
  170. context.save();
  171. }
  172. // If the canvas size is not changing, manually force state reset
  173. else
  174. layer.reset();
  175. // Assign new layer dimensions
  176. layer.width = newWidth;
  177. layer.height = newHeight;
  178. };
  179. /**
  180. * Given the X and Y coordinates of the upper-left corner of a rectangle
  181. * and the rectangle's width and height, resize the backing canvas element
  182. * as necessary to ensure that the rectangle fits within the canvas
  183. * element's coordinate space. This function will only make the canvas
  184. * larger. If the rectangle already fits within the canvas element's
  185. * coordinate space, the canvas is left unchanged.
  186. *
  187. * @private
  188. * @param {!number} x
  189. * The X coordinate of the upper-left corner of the rectangle to fit.
  190. *
  191. * @param {!number} y
  192. * The Y coordinate of the upper-left corner of the rectangle to fit.
  193. *
  194. * @param {!number} w
  195. * The width of the rectangle to fit.
  196. *
  197. * @param {!number} h
  198. * The height of the rectangle to fit.
  199. */
  200. function fitRect(x, y, w, h) {
  201. // Calculate bounds
  202. var opBoundX = w + x;
  203. var opBoundY = h + y;
  204. // Determine max width
  205. var resizeWidth;
  206. if (opBoundX > layer.width)
  207. resizeWidth = opBoundX;
  208. else
  209. resizeWidth = layer.width;
  210. // Determine max height
  211. var resizeHeight;
  212. if (opBoundY > layer.height)
  213. resizeHeight = opBoundY;
  214. else
  215. resizeHeight = layer.height;
  216. // Resize if necessary
  217. layer.resize(resizeWidth, resizeHeight);
  218. }
  219. /**
  220. * Set to true if this Layer should resize itself to accommodate the
  221. * dimensions of any drawing operation, and false (the default) otherwise.
  222. *
  223. * Note that setting this property takes effect immediately, and thus may
  224. * take effect on operations that were started in the past but have not
  225. * yet completed. If you wish the setting of this flag to only modify
  226. * future operations, you will need to make the setting of this flag an
  227. * operation with sync().
  228. *
  229. * @example
  230. * // Set autosize to true for all future operations
  231. * layer.sync(function() {
  232. * layer.autosize = true;
  233. * });
  234. *
  235. * @type {!boolean}
  236. * @default false
  237. */
  238. this.autosize = false;
  239. /**
  240. * The current width of this layer.
  241. *
  242. * @type {!number}
  243. */
  244. this.width = width;
  245. /**
  246. * The current height of this layer.
  247. *
  248. * @type {!number}
  249. */
  250. this.height = height;
  251. /**
  252. * Returns the canvas element backing this Layer. Note that the dimensions
  253. * of the canvas may not exactly match those of the Layer, as resizing a
  254. * canvas while maintaining its state is an expensive operation.
  255. *
  256. * @returns {!HTMLCanvasElement}
  257. * The canvas element backing this Layer.
  258. */
  259. this.getCanvas = function getCanvas() {
  260. return canvas;
  261. };
  262. /**
  263. * Returns a new canvas element containing the same image as this Layer.
  264. * Unlike getCanvas(), the canvas element returned is guaranteed to have
  265. * the exact same dimensions as the Layer.
  266. *
  267. * @returns {!HTMLCanvasElement}
  268. * A new canvas element containing a copy of the image content this
  269. * Layer.
  270. */
  271. this.toCanvas = function toCanvas() {
  272. // Create new canvas having same dimensions
  273. var canvas = document.createElement('canvas');
  274. canvas.width = layer.width;
  275. canvas.height = layer.height;
  276. // Copy image contents to new canvas
  277. var context = canvas.getContext('2d');
  278. context.drawImage(layer.getCanvas(), 0, 0);
  279. return canvas;
  280. };
  281. /**
  282. * Changes the size of this Layer to the given width and height. Resizing
  283. * is only attempted if the new size provided is actually different from
  284. * the current size.
  285. *
  286. * @param {!number} newWidth
  287. * The new width to assign to this Layer.
  288. *
  289. * @param {!number} newHeight
  290. * The new height to assign to this Layer.
  291. */
  292. this.resize = function(newWidth, newHeight) {
  293. if (newWidth !== layer.width || newHeight !== layer.height)
  294. resize(newWidth, newHeight);
  295. };
  296. /**
  297. * Draws the specified image at the given coordinates. The image specified
  298. * must already be loaded.
  299. *
  300. * @param {!number} x
  301. * The destination X coordinate.
  302. *
  303. * @param {!number} y
  304. * The destination Y coordinate.
  305. *
  306. * @param {!CanvasImageSource} image
  307. * The image to draw. Note that this is not a URL.
  308. */
  309. this.drawImage = function(x, y, image) {
  310. if (layer.autosize) fitRect(x, y, image.width, image.height);
  311. context.drawImage(image, x, y);
  312. empty = false;
  313. };
  314. /**
  315. * Transfer a rectangle of image data from one Layer to this Layer using the
  316. * specified transfer function.
  317. *
  318. * @param {!Guacamole.Layer} srcLayer
  319. * The Layer to copy image data from.
  320. *
  321. * @param {!number} srcx
  322. * The X coordinate of the upper-left corner of the rectangle within
  323. * the source Layer's coordinate space to copy data from.
  324. *
  325. * @param {!number} srcy
  326. * The Y coordinate of the upper-left corner of the rectangle within
  327. * the source Layer's coordinate space to copy data from.
  328. *
  329. * @param {!number} srcw
  330. * The width of the rectangle within the source Layer's coordinate
  331. * space to copy data from.
  332. *
  333. * @param {!number} srch
  334. * The height of the rectangle within the source Layer's coordinate
  335. * space to copy data from.
  336. *
  337. * @param {!number} x
  338. * The destination X coordinate.
  339. *
  340. * @param {!number} y
  341. * The destination Y coordinate.
  342. *
  343. * @param {!function} transferFunction
  344. * The transfer function to use to transfer data from source to
  345. * destination.
  346. */
  347. this.transfer = function(srcLayer, srcx, srcy, srcw, srch, x, y, transferFunction) {
  348. var srcCanvas = srcLayer.getCanvas();
  349. // If entire rectangle outside source canvas, stop
  350. if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return;
  351. // Otherwise, clip rectangle to area
  352. if (srcx + srcw > srcCanvas.width)
  353. srcw = srcCanvas.width - srcx;
  354. if (srcy + srch > srcCanvas.height)
  355. srch = srcCanvas.height - srcy;
  356. // Stop if nothing to draw.
  357. if (srcw === 0 || srch === 0) return;
  358. if (layer.autosize) fitRect(x, y, srcw, srch);
  359. // Get image data from src and dst
  360. var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch);
  361. var dst = context.getImageData(x , y, srcw, srch);
  362. // Apply transfer for each pixel
  363. for (var i=0; i<srcw*srch*4; i+=4) {
  364. // Get source pixel environment
  365. var src_pixel = new Guacamole.Layer.Pixel(
  366. src.data[i],
  367. src.data[i+1],
  368. src.data[i+2],
  369. src.data[i+3]
  370. );
  371. // Get destination pixel environment
  372. var dst_pixel = new Guacamole.Layer.Pixel(
  373. dst.data[i],
  374. dst.data[i+1],
  375. dst.data[i+2],
  376. dst.data[i+3]
  377. );
  378. // Apply transfer function
  379. transferFunction(src_pixel, dst_pixel);
  380. // Save pixel data
  381. dst.data[i ] = dst_pixel.red;
  382. dst.data[i+1] = dst_pixel.green;
  383. dst.data[i+2] = dst_pixel.blue;
  384. dst.data[i+3] = dst_pixel.alpha;
  385. }
  386. // Draw image data
  387. context.putImageData(dst, x, y);
  388. empty = false;
  389. };
  390. /**
  391. * Put a rectangle of image data from one Layer to this Layer directly
  392. * without performing any alpha blending. Simply copy the data.
  393. *
  394. * @param {!Guacamole.Layer} srcLayer
  395. * The Layer to copy image data from.
  396. *
  397. * @param {!number} srcx
  398. * The X coordinate of the upper-left corner of the rectangle within
  399. * the source Layer's coordinate space to copy data from.
  400. *
  401. * @param {!number} srcy
  402. * The Y coordinate of the upper-left corner of the rectangle within
  403. * the source Layer's coordinate space to copy data from.
  404. *
  405. * @param {!number} srcw
  406. * The width of the rectangle within the source Layer's coordinate
  407. * space to copy data from.
  408. *
  409. * @param {!number} srch
  410. * The height of the rectangle within the source Layer's coordinate
  411. * space to copy data from.
  412. *
  413. * @param {!number} x
  414. * The destination X coordinate.
  415. *
  416. * @param {!number} y
  417. * The destination Y coordinate.
  418. */
  419. this.put = function(srcLayer, srcx, srcy, srcw, srch, x, y) {
  420. var srcCanvas = srcLayer.getCanvas();
  421. // If entire rectangle outside source canvas, stop
  422. if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return;
  423. // Otherwise, clip rectangle to area
  424. if (srcx + srcw > srcCanvas.width)
  425. srcw = srcCanvas.width - srcx;
  426. if (srcy + srch > srcCanvas.height)
  427. srch = srcCanvas.height - srcy;
  428. // Stop if nothing to draw.
  429. if (srcw === 0 || srch === 0) return;
  430. if (layer.autosize) fitRect(x, y, srcw, srch);
  431. // Get image data from src and dst
  432. var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch);
  433. context.putImageData(src, x, y);
  434. empty = false;
  435. };
  436. /**
  437. * Copy a rectangle of image data from one Layer to this Layer. This
  438. * operation will copy exactly the image data that will be drawn once all
  439. * operations of the source Layer that were pending at the time this
  440. * function was called are complete. This operation will not alter the
  441. * size of the source Layer even if its autosize property is set to true.
  442. *
  443. * @param {!Guacamole.Layer} srcLayer
  444. * The Layer to copy image data from.
  445. *
  446. * @param {!number} srcx
  447. * The X coordinate of the upper-left corner of the rectangle within
  448. * the source Layer's coordinate space to copy data from.
  449. *
  450. * @param {!number} srcy
  451. * The Y coordinate of the upper-left corner of the rectangle within
  452. * the source Layer's coordinate space to copy data from.
  453. *
  454. * @param {!number} srcw
  455. * The width of the rectangle within the source Layer's coordinate
  456. * space to copy data from.
  457. *
  458. * @param {!number} srch
  459. * The height of the rectangle within the source Layer's coordinate
  460. * space to copy data from.
  461. *
  462. * @param {!number} x
  463. * The destination X coordinate.
  464. *
  465. * @param {!number} y
  466. * The destination Y coordinate.
  467. */
  468. this.copy = function(srcLayer, srcx, srcy, srcw, srch, x, y) {
  469. var srcCanvas = srcLayer.getCanvas();
  470. // If entire rectangle outside source canvas, stop
  471. if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return;
  472. // Otherwise, clip rectangle to area
  473. if (srcx + srcw > srcCanvas.width)
  474. srcw = srcCanvas.width - srcx;
  475. if (srcy + srch > srcCanvas.height)
  476. srch = srcCanvas.height - srcy;
  477. // Stop if nothing to draw.
  478. if (srcw === 0 || srch === 0) return;
  479. if (layer.autosize) fitRect(x, y, srcw, srch);
  480. context.drawImage(srcCanvas, srcx, srcy, srcw, srch, x, y, srcw, srch);
  481. empty = false;
  482. };
  483. /**
  484. * Starts a new path at the specified point.
  485. *
  486. * @param {!number} x
  487. * The X coordinate of the point to draw.
  488. *
  489. * @param {!number} y
  490. * The Y coordinate of the point to draw.
  491. */
  492. this.moveTo = function(x, y) {
  493. // Start a new path if current path is closed
  494. if (pathClosed) {
  495. context.beginPath();
  496. pathClosed = false;
  497. }
  498. if (layer.autosize) fitRect(x, y, 0, 0);
  499. context.moveTo(x, y);
  500. };
  501. /**
  502. * Add the specified line to the current path.
  503. *
  504. * @param {!number} x
  505. * The X coordinate of the endpoint of the line to draw.
  506. *
  507. * @param {!number} y
  508. * The Y coordinate of the endpoint of the line to draw.
  509. */
  510. this.lineTo = function(x, y) {
  511. // Start a new path if current path is closed
  512. if (pathClosed) {
  513. context.beginPath();
  514. pathClosed = false;
  515. }
  516. if (layer.autosize) fitRect(x, y, 0, 0);
  517. context.lineTo(x, y);
  518. };
  519. /**
  520. * Add the specified arc to the current path.
  521. *
  522. * @param {!number} x
  523. * The X coordinate of the center of the circle which will contain the
  524. * arc.
  525. *
  526. * @param {!number} y
  527. * The Y coordinate of the center of the circle which will contain the
  528. * arc.
  529. *
  530. * @param {!number} radius
  531. * The radius of the circle.
  532. *
  533. * @param {!number} startAngle
  534. * The starting angle of the arc, in radians.
  535. *
  536. * @param {!number} endAngle
  537. * The ending angle of the arc, in radians.
  538. *
  539. * @param {!boolean} negative
  540. * Whether the arc should be drawn in order of decreasing angle.
  541. */
  542. this.arc = function(x, y, radius, startAngle, endAngle, negative) {
  543. // Start a new path if current path is closed
  544. if (pathClosed) {
  545. context.beginPath();
  546. pathClosed = false;
  547. }
  548. if (layer.autosize) fitRect(x, y, 0, 0);
  549. context.arc(x, y, radius, startAngle, endAngle, negative);
  550. };
  551. /**
  552. * Starts a new path at the specified point.
  553. *
  554. * @param {!number} cp1x
  555. * The X coordinate of the first control point.
  556. *
  557. * @param {!number} cp1y
  558. * The Y coordinate of the first control point.
  559. *
  560. * @param {!number} cp2x
  561. * The X coordinate of the second control point.
  562. *
  563. * @param {!number} cp2y
  564. * The Y coordinate of the second control point.
  565. *
  566. * @param {!number} x
  567. * The X coordinate of the endpoint of the curve.
  568. *
  569. * @param {!number} y
  570. * The Y coordinate of the endpoint of the curve.
  571. */
  572. this.curveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
  573. // Start a new path if current path is closed
  574. if (pathClosed) {
  575. context.beginPath();
  576. pathClosed = false;
  577. }
  578. if (layer.autosize) fitRect(x, y, 0, 0);
  579. context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
  580. };
  581. /**
  582. * Closes the current path by connecting the end point with the start
  583. * point (if any) with a straight line.
  584. */
  585. this.close = function() {
  586. context.closePath();
  587. pathClosed = true;
  588. };
  589. /**
  590. * Add the specified rectangle to the current path.
  591. *
  592. * @param {!number} x
  593. * The X coordinate of the upper-left corner of the rectangle to draw.
  594. *
  595. * @param {!number} y
  596. * The Y coordinate of the upper-left corner of the rectangle to draw.
  597. *
  598. * @param {!number} w
  599. * The width of the rectangle to draw.
  600. *
  601. * @param {!number} h
  602. * The height of the rectangle to draw.
  603. */
  604. this.rect = function(x, y, w, h) {
  605. // Start a new path if current path is closed
  606. if (pathClosed) {
  607. context.beginPath();
  608. pathClosed = false;
  609. }
  610. if (layer.autosize) fitRect(x, y, w, h);
  611. context.rect(x, y, w, h);
  612. };
  613. /**
  614. * Clip all future drawing operations by the current path. The current path
  615. * is implicitly closed. The current path can continue to be reused
  616. * for other operations (such as fillColor()) but a new path will be started
  617. * once a path drawing operation (path() or rect()) is used.
  618. */
  619. this.clip = function() {
  620. // Set new clipping region
  621. context.clip();
  622. // Path now implicitly closed
  623. pathClosed = true;
  624. };
  625. /**
  626. * Stroke the current path with the specified color. The current path
  627. * is implicitly closed. The current path can continue to be reused
  628. * for other operations (such as clip()) but a new path will be started
  629. * once a path drawing operation (path() or rect()) is used.
  630. *
  631. * @param {!string} cap
  632. * The line cap style. Can be "round", "square", or "butt".
  633. *
  634. * @param {!string} join
  635. * The line join style. Can be "round", "bevel", or "miter".
  636. *
  637. * @param {!number} thickness
  638. * The line thickness in pixels.
  639. *
  640. * @param {!number} r
  641. * The red component of the color to fill.
  642. *
  643. * @param {!number} g
  644. * The green component of the color to fill.
  645. *
  646. * @param {!number} b
  647. * The blue component of the color to fill.
  648. *
  649. * @param {!number} a
  650. * The alpha component of the color to fill.
  651. */
  652. this.strokeColor = function(cap, join, thickness, r, g, b, a) {
  653. // Stroke with color
  654. context.lineCap = cap;
  655. context.lineJoin = join;
  656. context.lineWidth = thickness;
  657. context.strokeStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")";
  658. context.stroke();
  659. empty = false;
  660. // Path now implicitly closed
  661. pathClosed = true;
  662. };
  663. /**
  664. * Fills the current path with the specified color. The current path
  665. * is implicitly closed. The current path can continue to be reused
  666. * for other operations (such as clip()) but a new path will be started
  667. * once a path drawing operation (path() or rect()) is used.
  668. *
  669. * @param {!number} r
  670. * The red component of the color to fill.
  671. *
  672. * @param {!number} g
  673. * The green component of the color to fill.
  674. *
  675. * @param {!number} b
  676. * The blue component of the color to fill.
  677. *
  678. * @param {!number} a
  679. * The alpha component of the color to fill.
  680. */
  681. this.fillColor = function(r, g, b, a) {
  682. // Fill with color
  683. context.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")";
  684. context.fill();
  685. empty = false;
  686. // Path now implicitly closed
  687. pathClosed = true;
  688. };
  689. /**
  690. * Stroke the current path with the image within the specified layer. The
  691. * image data will be tiled infinitely within the stroke. The current path
  692. * is implicitly closed. The current path can continue to be reused
  693. * for other operations (such as clip()) but a new path will be started
  694. * once a path drawing operation (path() or rect()) is used.
  695. *
  696. * @param {!string} cap
  697. * The line cap style. Can be "round", "square", or "butt".
  698. *
  699. * @param {!string} join
  700. * The line join style. Can be "round", "bevel", or "miter".
  701. *
  702. * @param {!number} thickness
  703. * The line thickness in pixels.
  704. *
  705. * @param {!Guacamole.Layer} srcLayer
  706. * The layer to use as a repeating pattern within the stroke.
  707. */
  708. this.strokeLayer = function(cap, join, thickness, srcLayer) {
  709. // Stroke with image data
  710. context.lineCap = cap;
  711. context.lineJoin = join;
  712. context.lineWidth = thickness;
  713. context.strokeStyle = context.createPattern(
  714. srcLayer.getCanvas(),
  715. "repeat"
  716. );
  717. context.stroke();
  718. empty = false;
  719. // Path now implicitly closed
  720. pathClosed = true;
  721. };
  722. /**
  723. * Fills the current path with the image within the specified layer. The
  724. * image data will be tiled infinitely within the stroke. The current path
  725. * is implicitly closed. The current path can continue to be reused
  726. * for other operations (such as clip()) but a new path will be started
  727. * once a path drawing operation (path() or rect()) is used.
  728. *
  729. * @param {!Guacamole.Layer} srcLayer
  730. * The layer to use as a repeating pattern within the fill.
  731. */
  732. this.fillLayer = function(srcLayer) {
  733. // Fill with image data
  734. context.fillStyle = context.createPattern(
  735. srcLayer.getCanvas(),
  736. "repeat"
  737. );
  738. context.fill();
  739. empty = false;
  740. // Path now implicitly closed
  741. pathClosed = true;
  742. };
  743. /**
  744. * Push current layer state onto stack.
  745. */
  746. this.push = function() {
  747. // Save current state onto stack
  748. context.save();
  749. stackSize++;
  750. };
  751. /**
  752. * Pop layer state off stack.
  753. */
  754. this.pop = function() {
  755. // Restore current state from stack
  756. if (stackSize > 0) {
  757. context.restore();
  758. stackSize--;
  759. }
  760. };
  761. /**
  762. * Reset the layer, clearing the stack, the current path, and any transform
  763. * matrix.
  764. */
  765. this.reset = function() {
  766. // Clear stack
  767. while (stackSize > 0) {
  768. context.restore();
  769. stackSize--;
  770. }
  771. // Restore to initial state
  772. context.restore();
  773. context.save();
  774. // Clear path
  775. context.beginPath();
  776. pathClosed = false;
  777. };
  778. /**
  779. * Sets the given affine transform (defined with six values from the
  780. * transform's matrix).
  781. *
  782. * @param {!number} a
  783. * The first value in the affine transform's matrix.
  784. *
  785. * @param {!number} b
  786. * The second value in the affine transform's matrix.
  787. *
  788. * @param {!number} c
  789. * The third value in the affine transform's matrix.
  790. *
  791. * @param {!number} d
  792. * The fourth value in the affine transform's matrix.
  793. *
  794. * @param {!number} e
  795. * The fifth value in the affine transform's matrix.
  796. *
  797. * @param {!number} f
  798. * The sixth value in the affine transform's matrix.
  799. */
  800. this.setTransform = function(a, b, c, d, e, f) {
  801. context.setTransform(
  802. a, b, c,
  803. d, e, f
  804. /*0, 0, 1*/
  805. );
  806. };
  807. /**
  808. * Applies the given affine transform (defined with six values from the
  809. * transform's matrix).
  810. *
  811. * @param {!number} a
  812. * The first value in the affine transform's matrix.
  813. *
  814. * @param {!number} b
  815. * The second value in the affine transform's matrix.
  816. *
  817. * @param {!number} c
  818. * The third value in the affine transform's matrix.
  819. *
  820. * @param {!number} d
  821. * The fourth value in the affine transform's matrix.
  822. *
  823. * @param {!number} e
  824. * The fifth value in the affine transform's matrix.
  825. *
  826. * @param {!number} f
  827. * The sixth value in the affine transform's matrix.
  828. */
  829. this.transform = function(a, b, c, d, e, f) {
  830. context.transform(
  831. a, b, c,
  832. d, e, f
  833. /*0, 0, 1*/
  834. );
  835. };
  836. /**
  837. * Sets the channel mask for future operations on this Layer.
  838. *
  839. * The channel mask is a Guacamole-specific compositing operation identifier
  840. * with a single bit representing each of four channels (in order): source
  841. * image where destination transparent, source where destination opaque,
  842. * destination where source transparent, and destination where source
  843. * opaque.
  844. *
  845. * @param {!number} mask
  846. * The channel mask for future operations on this Layer.
  847. */
  848. this.setChannelMask = function(mask) {
  849. context.globalCompositeOperation = compositeOperation[mask];
  850. };
  851. /**
  852. * Sets the miter limit for stroke operations using the miter join. This
  853. * limit is the maximum ratio of the size of the miter join to the stroke
  854. * width. If this ratio is exceeded, the miter will not be drawn for that
  855. * joint of the path.
  856. *
  857. * @param {!number} limit
  858. * The miter limit for stroke operations using the miter join.
  859. */
  860. this.setMiterLimit = function(limit) {
  861. context.miterLimit = limit;
  862. };
  863. // Initialize canvas dimensions
  864. resize(width, height);
  865. // Explicitly render canvas below other elements in the layer (such as
  866. // child layers). Chrome and others may fail to render layers properly
  867. // without this.
  868. canvas.style.zIndex = -1;
  869. };
  870. /**
  871. * Channel mask for the composite operation "rout".
  872. *
  873. * @type {!number}
  874. */
  875. Guacamole.Layer.ROUT = 0x2;
  876. /**
  877. * Channel mask for the composite operation "atop".
  878. *
  879. * @type {!number}
  880. */
  881. Guacamole.Layer.ATOP = 0x6;
  882. /**
  883. * Channel mask for the composite operation "xor".
  884. *
  885. * @type {!number}
  886. */
  887. Guacamole.Layer.XOR = 0xA;
  888. /**
  889. * Channel mask for the composite operation "rover".
  890. *
  891. * @type {!number}
  892. */
  893. Guacamole.Layer.ROVER = 0xB;
  894. /**
  895. * Channel mask for the composite operation "over".
  896. *
  897. * @type {!number}
  898. */
  899. Guacamole.Layer.OVER = 0xE;
  900. /**
  901. * Channel mask for the composite operation "plus".
  902. *
  903. * @type {!number}
  904. */
  905. Guacamole.Layer.PLUS = 0xF;
  906. /**
  907. * Channel mask for the composite operation "rin".
  908. * Beware that WebKit-based browsers may leave the contents of the destination
  909. * layer where the source layer is transparent, despite the definition of this
  910. * operation.
  911. *
  912. * @type {!number}
  913. */
  914. Guacamole.Layer.RIN = 0x1;
  915. /**
  916. * Channel mask for the composite operation "in".
  917. * Beware that WebKit-based browsers may leave the contents of the destination
  918. * layer where the source layer is transparent, despite the definition of this
  919. * operation.
  920. *
  921. * @type {!number}
  922. */
  923. Guacamole.Layer.IN = 0x4;
  924. /**
  925. * Channel mask for the composite operation "out".
  926. * Beware that WebKit-based browsers may leave the contents of the destination
  927. * layer where the source layer is transparent, despite the definition of this
  928. * operation.
  929. *
  930. * @type {!number}
  931. */
  932. Guacamole.Layer.OUT = 0x8;
  933. /**
  934. * Channel mask for the composite operation "ratop".
  935. * Beware that WebKit-based browsers may leave the contents of the destination
  936. * layer where the source layer is transparent, despite the definition of this
  937. * operation.
  938. *
  939. * @type {!number}
  940. */
  941. Guacamole.Layer.RATOP = 0x9;
  942. /**
  943. * Channel mask for the composite operation "src".
  944. * Beware that WebKit-based browsers may leave the contents of the destination
  945. * layer where the source layer is transparent, despite the definition of this
  946. * operation.
  947. *
  948. * @type {!number}
  949. */
  950. Guacamole.Layer.SRC = 0xC;
  951. /**
  952. * Represents a single pixel of image data. All components have a minimum value
  953. * of 0 and a maximum value of 255.
  954. *
  955. * @constructor
  956. *
  957. * @param {!number} r
  958. * The red component of this pixel.
  959. *
  960. * @param {!number} g
  961. * The green component of this pixel.
  962. *
  963. * @param {!number} b
  964. * The blue component of this pixel.
  965. *
  966. * @param {!number} a
  967. * The alpha component of this pixel.
  968. */
  969. Guacamole.Layer.Pixel = function(r, g, b, a) {
  970. /**
  971. * The red component of this pixel, where 0 is the minimum value,
  972. * and 255 is the maximum.
  973. *
  974. * @type {!number}
  975. */
  976. this.red = r;
  977. /**
  978. * The green component of this pixel, where 0 is the minimum value,
  979. * and 255 is the maximum.
  980. *
  981. * @type {!number}
  982. */
  983. this.green = g;
  984. /**
  985. * The blue component of this pixel, where 0 is the minimum value,
  986. * and 255 is the maximum.
  987. *
  988. * @type {!number}
  989. */
  990. this.blue = b;
  991. /**
  992. * The alpha component of this pixel, where 0 is the minimum value,
  993. * and 255 is the maximum.
  994. *
  995. * @type {!number}
  996. */
  997. this.alpha = a;
  998. };