image.ts 28 KB

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