image.ts 28 KB

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