image.ts 29 KB

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