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