image.ts 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. import { Nullable } from "babylonjs/types";
  2. import { Observable } from "babylonjs/Misc/observable";
  3. import { Tools } from "babylonjs/Misc/tools";
  4. import { Control } from "./control";
  5. import { Measure } from "../measure";
  6. /**
  7. * Class used to create 2D images
  8. */
  9. export class Image extends Control {
  10. private static _WorkingCanvas: Nullable<HTMLCanvasElement> = null;
  11. private _domImage: HTMLImageElement;
  12. private _imageWidth: number;
  13. private _imageHeight: number;
  14. private _loaded = false;
  15. private _stretch = Image.STRETCH_FILL;
  16. private _source: Nullable<string>;
  17. private _autoScale = false;
  18. private _sourceLeft = 0;
  19. private _sourceTop = 0;
  20. private _sourceWidth = 0;
  21. private _sourceHeight = 0;
  22. private _cellWidth: number = 0;
  23. private _cellHeight: number = 0;
  24. private _cellId: number = -1;
  25. private _populateNinePatchSlicesFromImage = false;
  26. private _sliceLeft: number;
  27. private _sliceRight: number;
  28. private _sliceTop: number;
  29. private _sliceBottom: number;
  30. private _detectPointerOnOpaqueOnly: boolean;
  31. /**
  32. * Observable notified when the content is loaded
  33. */
  34. public onImageLoadedObservable = new Observable<Image>();
  35. /**
  36. * Observable notified when _sourceLeft, _sourceTop, _sourceWidth and _sourceHeight are computed
  37. */
  38. public onSVGAttributesComputedObservable = new Observable<Image>();
  39. /**
  40. * Gets a boolean indicating that the content is loaded
  41. */
  42. public get isLoaded(): boolean {
  43. return this._loaded;
  44. }
  45. /**
  46. * Gets or sets a boolean indicating if nine patch slices (left, top, right, bottom) should be read from image data
  47. */
  48. public get populateNinePatchSlicesFromImage(): boolean {
  49. return this._populateNinePatchSlicesFromImage;
  50. }
  51. public set populateNinePatchSlicesFromImage(value: boolean) {
  52. if (this._populateNinePatchSlicesFromImage === value) {
  53. return;
  54. }
  55. this._populateNinePatchSlicesFromImage = value;
  56. if (this._populateNinePatchSlicesFromImage && this._loaded) {
  57. this._extractNinePatchSliceDataFromImage();
  58. }
  59. }
  60. /**
  61. * Gets or sets a boolean indicating if pointers should only be validated on pixels with alpha > 0.
  62. * Beware using this as this will comsume more memory as the image has to be stored twice
  63. */
  64. public get detectPointerOnOpaqueOnly(): boolean {
  65. return this._detectPointerOnOpaqueOnly;
  66. }
  67. public set detectPointerOnOpaqueOnly(value: boolean) {
  68. if (this._detectPointerOnOpaqueOnly === value) {
  69. return;
  70. }
  71. this._detectPointerOnOpaqueOnly = value;
  72. }
  73. /**
  74. * Gets or sets the left value for slicing (9-patch)
  75. */
  76. public get sliceLeft(): number {
  77. return this._sliceLeft;
  78. }
  79. public set sliceLeft(value: number) {
  80. if (this._sliceLeft === value) {
  81. return;
  82. }
  83. this._sliceLeft = value;
  84. this._markAsDirty();
  85. }
  86. /**
  87. * Gets or sets the right value for slicing (9-patch)
  88. */
  89. public get sliceRight(): number {
  90. return this._sliceRight;
  91. }
  92. public set sliceRight(value: number) {
  93. if (this._sliceRight === value) {
  94. return;
  95. }
  96. this._sliceRight = value;
  97. this._markAsDirty();
  98. }
  99. /**
  100. * Gets or sets the top value for slicing (9-patch)
  101. */
  102. public get sliceTop(): number {
  103. return this._sliceTop;
  104. }
  105. public set sliceTop(value: number) {
  106. if (this._sliceTop === value) {
  107. return;
  108. }
  109. this._sliceTop = value;
  110. this._markAsDirty();
  111. }
  112. /**
  113. * Gets or sets the bottom value for slicing (9-patch)
  114. */
  115. public get sliceBottom(): number {
  116. return this._sliceBottom;
  117. }
  118. public set sliceBottom(value: number) {
  119. if (this._sliceBottom === value) {
  120. return;
  121. }
  122. this._sliceBottom = value;
  123. this._markAsDirty();
  124. }
  125. /**
  126. * Gets or sets the left coordinate in the source image
  127. */
  128. public get sourceLeft(): number {
  129. return this._sourceLeft;
  130. }
  131. public set sourceLeft(value: number) {
  132. if (this._sourceLeft === value) {
  133. return;
  134. }
  135. this._sourceLeft = value;
  136. this._markAsDirty();
  137. }
  138. /**
  139. * Gets or sets the top coordinate in the source image
  140. */
  141. public get sourceTop(): number {
  142. return this._sourceTop;
  143. }
  144. public set sourceTop(value: number) {
  145. if (this._sourceTop === value) {
  146. return;
  147. }
  148. this._sourceTop = value;
  149. this._markAsDirty();
  150. }
  151. /**
  152. * Gets or sets the width to capture in the source image
  153. */
  154. public get sourceWidth(): number {
  155. return this._sourceWidth;
  156. }
  157. public set sourceWidth(value: number) {
  158. if (this._sourceWidth === value) {
  159. return;
  160. }
  161. this._sourceWidth = value;
  162. this._markAsDirty();
  163. }
  164. /**
  165. * Gets or sets the height to capture in the source image
  166. */
  167. public get sourceHeight(): number {
  168. return this._sourceHeight;
  169. }
  170. public set sourceHeight(value: number) {
  171. if (this._sourceHeight === value) {
  172. return;
  173. }
  174. this._sourceHeight = value;
  175. this._markAsDirty();
  176. }
  177. /**
  178. * Gets or sets a boolean indicating if the image can force its container to adapt its size
  179. * @see http://doc.babylonjs.com/how_to/gui#image
  180. */
  181. public get autoScale(): boolean {
  182. return this._autoScale;
  183. }
  184. public set autoScale(value: boolean) {
  185. if (this._autoScale === value) {
  186. return;
  187. }
  188. this._autoScale = value;
  189. if (value && this._loaded) {
  190. this.synchronizeSizeWithContent();
  191. }
  192. }
  193. /** Gets or sets the streching mode used by the image */
  194. public get stretch(): number {
  195. return this._stretch;
  196. }
  197. public set stretch(value: number) {
  198. if (this._stretch === value) {
  199. return;
  200. }
  201. this._stretch = value;
  202. this._markAsDirty();
  203. }
  204. /**
  205. * Gets or sets the internal DOM image used to render the control
  206. */
  207. public set domImage(value: HTMLImageElement) {
  208. this._domImage = value;
  209. this._loaded = false;
  210. if (this._domImage.width) {
  211. this._onImageLoaded();
  212. } else {
  213. this._domImage.onload = () => {
  214. this._onImageLoaded();
  215. };
  216. }
  217. }
  218. public get domImage(): HTMLImageElement {
  219. return this._domImage;
  220. }
  221. private _onImageLoaded(): void {
  222. this._imageWidth = this._domImage.width;
  223. this._imageHeight = this._domImage.height;
  224. this._loaded = true;
  225. if (this._populateNinePatchSlicesFromImage) {
  226. this._extractNinePatchSliceDataFromImage();
  227. }
  228. if (this._autoScale) {
  229. this.synchronizeSizeWithContent();
  230. }
  231. this.onImageLoadedObservable.notifyObservers(this);
  232. this._markAsDirty();
  233. }
  234. private _extractNinePatchSliceDataFromImage() {
  235. if (!Image._WorkingCanvas) {
  236. Image._WorkingCanvas = document.createElement('canvas');
  237. }
  238. const canvas = Image._WorkingCanvas;
  239. const context = canvas.getContext('2d')!;
  240. const width = this._domImage.width;
  241. const height = this._domImage.height;
  242. canvas.width = width;
  243. canvas.height = height;
  244. context.drawImage(this._domImage, 0, 0, width, height);
  245. const imageData = context.getImageData(0, 0, width, height);
  246. // Left and right
  247. this._sliceLeft = -1;
  248. this._sliceRight = -1;
  249. for (var x = 0; x < width; x++) {
  250. const alpha = imageData.data[x * 4 + 3];
  251. if (alpha > 127 && this._sliceLeft === -1) {
  252. this._sliceLeft = x;
  253. continue;
  254. }
  255. if (alpha < 127 && this._sliceLeft > -1) {
  256. this._sliceRight = x;
  257. break;
  258. }
  259. }
  260. // top and bottom
  261. this._sliceTop = -1;
  262. this._sliceBottom = -1;
  263. for (var y = 0; y < height; y++) {
  264. const alpha = imageData.data[y * width * 4 + 3];
  265. if (alpha > 127 && this._sliceTop === -1) {
  266. this._sliceTop = y;
  267. continue;
  268. }
  269. if (alpha < 127 && this._sliceTop > -1) {
  270. this._sliceBottom = y;
  271. break;
  272. }
  273. }
  274. }
  275. /**
  276. * Gets or sets image source url
  277. */
  278. public set source(value: Nullable<string>) {
  279. if (this._source === value) {
  280. return;
  281. }
  282. this._loaded = false;
  283. this._source = value;
  284. if (value) {
  285. this._svgCheck(value);
  286. }
  287. this._domImage = document.createElement("img");
  288. this._domImage.onload = () => {
  289. this._onImageLoaded();
  290. };
  291. if (value) {
  292. Tools.SetCorsBehavior(value, this._domImage);
  293. this._domImage.src = value;
  294. }
  295. }
  296. /**
  297. * Checks for svg document with icon id present
  298. */
  299. private _svgCheck(value: string) {
  300. if ((value.search(/.svg#/gi) !== -1) && (value.indexOf("#") === value.lastIndexOf("#"))) {
  301. var svgsrc = value.split('#')[0];
  302. var elemid = value.split('#')[1];
  303. // check if object alr exist in document
  304. var svgExist = <HTMLObjectElement> document.body.querySelector('object[data="' + svgsrc + '"]');
  305. if (svgExist) {
  306. if (svgExist.contentDocument) {
  307. // svg object alr exists
  308. this._getSVGAttribs(svgExist, elemid);
  309. } else {
  310. console.log(elemid + " in here2");
  311. // wait for object to load
  312. svgExist.addEventListener("load", () => {
  313. this._getSVGAttribs(svgExist, elemid);
  314. });
  315. }
  316. } else {
  317. // create document object
  318. var svgImage = document.createElement("object");
  319. svgImage.data = svgsrc;
  320. svgImage.type = "image/svg+xml";
  321. svgImage.width = "0%";
  322. svgImage.height = "0%";
  323. document.body.appendChild(svgImage);
  324. // when the object has loaded, get the element attribs
  325. svgImage.onload = () => {
  326. var svgobj = <HTMLObjectElement> document.body.querySelector('object[data="' + svgsrc + '"]');
  327. if (svgobj) {
  328. this._getSVGAttribs(svgobj, elemid);
  329. }
  330. };
  331. }
  332. }
  333. }
  334. /**
  335. * Sets sourceLeft, sourceTop, sourceWidth, sourceHeight automatically
  336. * given external svg file and icon id
  337. */
  338. private _getSVGAttribs(svgsrc: HTMLObjectElement, elemid: string) {
  339. var svgDoc = svgsrc.contentDocument;
  340. // get viewbox width and height, get svg document width and height in px
  341. if (svgDoc && svgDoc.documentElement) {
  342. var vb = svgDoc.documentElement.getAttribute("viewBox");
  343. var docwidth = Number(svgDoc.documentElement.getAttribute("width"));
  344. var docheight = Number(svgDoc.documentElement.getAttribute("height"));
  345. // get element bbox and matrix transform
  346. var elem = <SVGGraphicsElement> <unknown> svgDoc.getElementById(elemid);
  347. if (elem instanceof SVGElement && vb && docwidth && docheight) {
  348. var vb_width = Number(vb.split(" ")[2]);
  349. var vb_height = Number(vb.split(" ")[3]);
  350. var elem_bbox = elem.getBBox();
  351. var elem_matrix_a = 1;
  352. var elem_matrix_d = 1;
  353. var elem_matrix_e = 0;
  354. var elem_matrix_f = 0;
  355. if (elem.transform && elem.transform.baseVal.consolidate()) {
  356. elem_matrix_a = elem.transform.baseVal.consolidate().matrix.a;
  357. elem_matrix_d = elem.transform.baseVal.consolidate().matrix.d;
  358. elem_matrix_e = elem.transform.baseVal.consolidate().matrix.e;
  359. elem_matrix_f = elem.transform.baseVal.consolidate().matrix.f;
  360. }
  361. // compute source coordinates and dimensions
  362. this.sourceLeft = ((elem_matrix_a * elem_bbox.x + elem_matrix_e) * docwidth) / vb_width;
  363. this.sourceTop = ((elem_matrix_d * elem_bbox.y + elem_matrix_f) * docheight) / vb_height;
  364. this.sourceWidth = (elem_bbox.width * elem_matrix_a) * (docwidth / vb_width);
  365. this.sourceHeight = (elem_bbox.height * elem_matrix_d) * (docheight / vb_height);
  366. this.onSVGAttributesComputedObservable.notifyObservers(this);
  367. }
  368. }
  369. }
  370. /**
  371. * Gets or sets the cell width to use when animation sheet is enabled
  372. * @see http://doc.babylonjs.com/how_to/gui#image
  373. */
  374. get cellWidth(): number {
  375. return this._cellWidth;
  376. }
  377. set cellWidth(value: number) {
  378. if (this._cellWidth === value) {
  379. return;
  380. }
  381. this._cellWidth = value;
  382. this._markAsDirty();
  383. }
  384. /**
  385. * Gets or sets the cell height to use when animation sheet is enabled
  386. * @see http://doc.babylonjs.com/how_to/gui#image
  387. */
  388. get cellHeight(): number {
  389. return this._cellHeight;
  390. }
  391. set cellHeight(value: number) {
  392. if (this._cellHeight === value) {
  393. return;
  394. }
  395. this._cellHeight = value;
  396. this._markAsDirty();
  397. }
  398. /**
  399. * Gets or sets the cell id to use (this will turn on the animation sheet mode)
  400. * @see http://doc.babylonjs.com/how_to/gui#image
  401. */
  402. get cellId(): number {
  403. return this._cellId;
  404. }
  405. set cellId(value: number) {
  406. if (this._cellId === value) {
  407. return;
  408. }
  409. this._cellId = value;
  410. this._markAsDirty();
  411. }
  412. /**
  413. * Creates a new Image
  414. * @param name defines the control name
  415. * @param url defines the image url
  416. */
  417. constructor(public name?: string, url: Nullable<string> = null) {
  418. super(name);
  419. this.source = url;
  420. }
  421. /**
  422. * Tests if a given coordinates belong to the current control
  423. * @param x defines x coordinate to test
  424. * @param y defines y coordinate to test
  425. * @returns true if the coordinates are inside the control
  426. */
  427. public contains(x: number, y: number): boolean {
  428. if (!super.contains(x, y)) {
  429. return false;
  430. }
  431. if (!this._detectPointerOnOpaqueOnly || !Image._WorkingCanvas) {
  432. return true;
  433. }
  434. const canvas = Image._WorkingCanvas;
  435. const context = canvas.getContext("2d")!;
  436. const width = this._currentMeasure.width | 0;
  437. const height = this._currentMeasure.height | 0;
  438. const imageData = context.getImageData(0, 0, width, height).data;
  439. x = (x - this._currentMeasure.left) | 0;
  440. y = (y - this._currentMeasure.top) | 0;
  441. const pickedPixel = imageData[(x + y * this._currentMeasure.width) * 4 + 3];
  442. return pickedPixel > 0;
  443. }
  444. protected _getTypeName(): string {
  445. return "Image";
  446. }
  447. /** Force the control to synchronize with its content */
  448. public synchronizeSizeWithContent() {
  449. if (!this._loaded) {
  450. return;
  451. }
  452. this.width = this._domImage.width + "px";
  453. this.height = this._domImage.height + "px";
  454. }
  455. protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
  456. if (this._loaded) {
  457. switch (this._stretch) {
  458. case Image.STRETCH_NONE:
  459. break;
  460. case Image.STRETCH_FILL:
  461. break;
  462. case Image.STRETCH_UNIFORM:
  463. break;
  464. case Image.STRETCH_NINE_PATCH:
  465. break;
  466. case Image.STRETCH_EXTEND:
  467. if (this._autoScale) {
  468. this.synchronizeSizeWithContent();
  469. }
  470. if (this.parent && this.parent.parent) { // Will update root size if root is not the top root
  471. this.parent.adaptWidthToChildren = true;
  472. this.parent.adaptHeightToChildren = true;
  473. }
  474. break;
  475. }
  476. }
  477. super._processMeasures(parentMeasure, context);
  478. }
  479. private _prepareWorkingCanvasForOpaqueDetection() {
  480. if (!this._detectPointerOnOpaqueOnly) {
  481. return;
  482. }
  483. if (!Image._WorkingCanvas) {
  484. Image._WorkingCanvas = document.createElement('canvas');
  485. }
  486. const canvas = Image._WorkingCanvas;
  487. const width = this._currentMeasure.width;
  488. const height = this._currentMeasure.height;
  489. const context = canvas.getContext("2d")!;
  490. canvas.width = width;
  491. canvas.height = height;
  492. context.clearRect(0, 0, width, height);
  493. }
  494. private _drawImage(context: CanvasRenderingContext2D, sx: number, sy: number, sw: number, sh: number, tx: number, ty: number, tw: number, th: number) {
  495. context.drawImage(this._domImage,
  496. sx, sy, sw, sh,
  497. tx, ty, tw, th);
  498. if (!this._detectPointerOnOpaqueOnly) {
  499. return;
  500. }
  501. const canvas = Image._WorkingCanvas!;
  502. context = canvas.getContext("2d")!;
  503. context.drawImage(this._domImage,
  504. sx, sy, sw, sh,
  505. tx - this._currentMeasure.left, ty - this._currentMeasure.top, tw, th);
  506. }
  507. public _draw(context: CanvasRenderingContext2D): void {
  508. context.save();
  509. if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
  510. context.shadowColor = this.shadowColor;
  511. context.shadowBlur = this.shadowBlur;
  512. context.shadowOffsetX = this.shadowOffsetX;
  513. context.shadowOffsetY = this.shadowOffsetY;
  514. }
  515. let x, y, width, height;
  516. if (this.cellId == -1) {
  517. x = this._sourceLeft;
  518. y = this._sourceTop;
  519. width = this._sourceWidth ? this._sourceWidth : this._imageWidth;
  520. height = this._sourceHeight ? this._sourceHeight : this._imageHeight;
  521. }
  522. else {
  523. let rowCount = this._domImage.naturalWidth / this.cellWidth;
  524. let column = (this.cellId / rowCount) >> 0;
  525. let row = this.cellId % rowCount;
  526. x = this.cellWidth * row;
  527. y = this.cellHeight * column;
  528. width = this.cellWidth;
  529. height = this.cellHeight;
  530. }
  531. this._prepareWorkingCanvasForOpaqueDetection();
  532. this._applyStates(context);
  533. if (this._loaded) {
  534. switch (this._stretch) {
  535. case Image.STRETCH_NONE:
  536. this._drawImage(context, x, y, width, height,
  537. this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
  538. break;
  539. case Image.STRETCH_FILL:
  540. this._drawImage(context, x, y, width, height,
  541. this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
  542. break;
  543. case Image.STRETCH_UNIFORM:
  544. var hRatio = this._currentMeasure.width / width;
  545. var vRatio = this._currentMeasure.height / height;
  546. var ratio = Math.min(hRatio, vRatio);
  547. var centerX = (this._currentMeasure.width - width * ratio) / 2;
  548. var centerY = (this._currentMeasure.height - height * ratio) / 2;
  549. this._drawImage(context, x, y, width, height,
  550. this._currentMeasure.left + centerX, this._currentMeasure.top + centerY, width * ratio, height * ratio);
  551. break;
  552. case Image.STRETCH_EXTEND:
  553. this._drawImage(context, x, y, width, height,
  554. this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
  555. break;
  556. case Image.STRETCH_NINE_PATCH:
  557. this._renderNinePatch(context);
  558. break;
  559. }
  560. }
  561. context.restore();
  562. }
  563. private _renderCornerPatch(context: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, targetX: number, targetY: number): void {
  564. this._drawImage(context, x, y, width, height, this._currentMeasure.left + targetX, this._currentMeasure.top + targetY, width, height);
  565. }
  566. private _renderNinePatch(context: CanvasRenderingContext2D): void {
  567. let height = this._imageHeight;
  568. let leftWidth = this._sliceLeft;
  569. let topHeight = this._sliceTop;
  570. let bottomHeight = this._imageHeight - this._sliceBottom;
  571. let rightWidth = this._imageWidth - this._sliceRight;
  572. let left = 0;
  573. let top = 0;
  574. if (this._populateNinePatchSlicesFromImage) {
  575. left = 1;
  576. top = 1;
  577. height -= 2;
  578. leftWidth -= 1;
  579. topHeight -= 1;
  580. bottomHeight -= 1;
  581. rightWidth -= 1;
  582. }
  583. const centerWidth = this._sliceRight - this._sliceLeft + 1;
  584. const targetCenterWidth = this._currentMeasure.width - rightWidth - this.sliceLeft + 1;
  585. const targetTopHeight = this._currentMeasure.height - height + this._sliceBottom;
  586. // Corners
  587. this._renderCornerPatch(context, left, top, leftWidth, topHeight, 0, 0);
  588. this._renderCornerPatch(context, left, this._sliceBottom, leftWidth, height - this._sliceBottom, 0, targetTopHeight);
  589. this._renderCornerPatch(context, this._sliceRight, top, rightWidth, topHeight, this._currentMeasure.width - rightWidth, 0);
  590. this._renderCornerPatch(context, this._sliceRight, this._sliceBottom, rightWidth, height - this._sliceBottom, this._currentMeasure.width - rightWidth, targetTopHeight);
  591. // Center
  592. this._drawImage(context, this._sliceLeft, this._sliceTop, centerWidth, this._sliceBottom - this._sliceTop + 1,
  593. this._currentMeasure.left + leftWidth, this._currentMeasure.top + topHeight, targetCenterWidth, targetTopHeight - topHeight + 1);
  594. // Borders
  595. this._drawImage(context, left, this._sliceTop, leftWidth, this._sliceBottom - this._sliceTop,
  596. this._currentMeasure.left, this._currentMeasure.top + topHeight, leftWidth, targetTopHeight - topHeight);
  597. this._drawImage(context, this._sliceRight, this._sliceTop, leftWidth, this._sliceBottom - this._sliceTop,
  598. this._currentMeasure.left + this._currentMeasure.width - rightWidth, this._currentMeasure.top + topHeight, leftWidth, targetTopHeight - topHeight);
  599. this._drawImage(context, this._sliceLeft, top, centerWidth, topHeight,
  600. this._currentMeasure.left + leftWidth, this._currentMeasure.top, targetCenterWidth, topHeight);
  601. this._drawImage(context, this._sliceLeft, this._sliceBottom, centerWidth, bottomHeight,
  602. this._currentMeasure.left + leftWidth, this._currentMeasure.top + targetTopHeight, targetCenterWidth, bottomHeight);
  603. }
  604. public dispose() {
  605. super.dispose();
  606. this.onImageLoadedObservable.clear();
  607. this.onSVGAttributesComputedObservable.clear();
  608. }
  609. // Static
  610. /** STRETCH_NONE */
  611. public static readonly STRETCH_NONE = 0;
  612. /** STRETCH_FILL */
  613. public static readonly STRETCH_FILL = 1;
  614. /** STRETCH_UNIFORM */
  615. public static readonly STRETCH_UNIFORM = 2;
  616. /** STRETCH_EXTEND */
  617. public static readonly STRETCH_EXTEND = 3;
  618. /** NINE_PATCH */
  619. public static readonly STRETCH_NINE_PATCH = 4;
  620. }