UNPKG

@babylonjs/gui

Version:

Babylon.js GUI module =====================

915 lines 35.1 kB
import { __decorate } from "@babylonjs/core/tslib.es6.js"; import { Observable } from "@babylonjs/core/Misc/observable.js"; import { Tools } from "@babylonjs/core/Misc/tools.js"; import { Control } from "./control.js"; import { RegisterClass } from "@babylonjs/core/Misc/typeStore.js"; import { serialize } from "@babylonjs/core/Misc/decorators.js"; import { EngineStore } from "@babylonjs/core/Engines/engineStore.js"; /** * Class used to create 2D images */ export class Image extends Control { /** * Gets a boolean indicating that the content is loaded */ get isLoaded() { return this._loaded; } isReady() { return this.isLoaded; } /** * Gets or sets a boolean indicating if pointers should only be validated on pixels with alpha > 0. * Beware using this as this will consume more memory as the image has to be stored twice */ get detectPointerOnOpaqueOnly() { return this._detectPointerOnOpaqueOnly; } set detectPointerOnOpaqueOnly(value) { if (this._detectPointerOnOpaqueOnly === value) { return; } this._detectPointerOnOpaqueOnly = value; } /** * Gets or sets the left value for slicing (9-patch) */ get sliceLeft() { return this._sliceLeft; } set sliceLeft(value) { if (this._sliceLeft === value) { return; } this._sliceLeft = value; this._markAsDirty(); } /** * Gets or sets the right value for slicing (9-patch) */ get sliceRight() { return this._sliceRight; } set sliceRight(value) { if (this._sliceRight === value) { return; } this._sliceRight = value; this._markAsDirty(); } /** * Gets or sets the top value for slicing (9-patch) */ get sliceTop() { return this._sliceTop; } set sliceTop(value) { if (this._sliceTop === value) { return; } this._sliceTop = value; this._markAsDirty(); } /** * Gets or sets the bottom value for slicing (9-patch) */ get sliceBottom() { return this._sliceBottom; } set sliceBottom(value) { if (this._sliceBottom === value) { return; } this._sliceBottom = value; this._markAsDirty(); } /** * Gets or sets the left coordinate in the source image */ get sourceLeft() { return this._sourceLeft; } set sourceLeft(value) { if (this._sourceLeft === value) { return; } this._sourceLeft = value; this._markAsDirty(); } /** * Gets or sets the top coordinate in the source image */ get sourceTop() { return this._sourceTop; } set sourceTop(value) { if (this._sourceTop === value) { return; } this._sourceTop = value; this._markAsDirty(); } /** * Gets or sets the width to capture in the source image */ get sourceWidth() { return this._sourceWidth; } set sourceWidth(value) { if (this._sourceWidth === value) { return; } this._sourceWidth = value; this._markAsDirty(); } /** * Gets or sets the height to capture in the source image */ get sourceHeight() { return this._sourceHeight; } set sourceHeight(value) { if (this._sourceHeight === value) { return; } this._sourceHeight = value; this._markAsDirty(); } /** * Gets the image width */ get imageWidth() { return this._imageWidth; } /** * Gets the image height */ get imageHeight() { return this._imageHeight; } /** * Gets or sets a boolean indicating if nine patch slices (left, top, right, bottom) should be read from image data */ get populateNinePatchSlicesFromImage() { return this._populateNinePatchSlicesFromImage; } set populateNinePatchSlicesFromImage(value) { if (this._populateNinePatchSlicesFromImage === value) { return; } this._populateNinePatchSlicesFromImage = value; if (this._populateNinePatchSlicesFromImage && this._loaded) { this._extractNinePatchSliceDataFromImage(); } } /** Indicates if the format of the image is SVG */ get isSVG() { return this._isSVG; } /** Gets the status of the SVG attributes computation (sourceLeft, sourceTop, sourceWidth, sourceHeight) */ get svgAttributesComputationCompleted() { return this._svgAttributesComputationCompleted; } /** * Gets or sets a boolean indicating if the image can force its container to adapt its size * @see https://doc.babylonjs.com/features/featuresDeepDive/gui/gui#image */ get autoScale() { return this._autoScale; } set autoScale(value) { if (this._autoScale === value) { return; } this._autoScale = value; if (value && this._loaded) { this.synchronizeSizeWithContent(); } } /** Gets or sets the stretching mode used by the image */ get stretch() { return this._stretch; } set stretch(value) { if (this._stretch === value) { return; } this._stretch = value; this._markAsDirty(); } /** * @internal */ _rotate90(n, preserveProperties = false) { const width = this._domImage.width; const height = this._domImage.height; // Should abstract platform instead of using LastCreatedEngine const engine = this._host?.getScene()?.getEngine() || EngineStore.LastCreatedEngine; if (!engine) { throw new Error("Invalid engine. Unable to create a canvas."); } const canvas = engine.createCanvas(height, width); const context = canvas.getContext("2d"); context.translate(canvas.width / 2, canvas.height / 2); context.rotate((n * Math.PI) / 2); context.drawImage(this._domImage, 0, 0, width, height, -width / 2, -height / 2, width, height); const dataUrl = canvas.toDataURL("image/jpg"); const rotatedImage = new Image(this.name + "rotated", dataUrl); if (preserveProperties) { rotatedImage._stretch = this._stretch; rotatedImage._autoScale = this._autoScale; rotatedImage._cellId = this._cellId; rotatedImage._cellWidth = n % 1 ? this._cellHeight : this._cellWidth; rotatedImage._cellHeight = n % 1 ? this._cellWidth : this._cellHeight; } this._handleRotationForSVGImage(this, rotatedImage, n); this._imageDataCache.data = null; return rotatedImage; } _handleRotationForSVGImage(srcImage, dstImage, n) { if (!srcImage._isSVG) { return; } if (srcImage._svgAttributesComputationCompleted) { this._rotate90SourceProperties(srcImage, dstImage, n); this._markAsDirty(); } else { srcImage.onSVGAttributesComputedObservable.addOnce(() => { this._rotate90SourceProperties(srcImage, dstImage, n); this._markAsDirty(); }); } } _rotate90SourceProperties(srcImage, dstImage, n) { let srcLeft = srcImage.sourceLeft, srcTop = srcImage.sourceTop, srcWidth = srcImage.domImage.width, srcHeight = srcImage.domImage.height; let dstLeft = srcLeft, dstTop = srcTop, dstWidth = srcImage.sourceWidth, dstHeight = srcImage.sourceHeight; if (n != 0) { const mult = n < 0 ? -1 : 1; n = n % 4; for (let i = 0; i < Math.abs(n); ++i) { dstLeft = -(srcTop - srcHeight / 2) * mult + srcHeight / 2; dstTop = (srcLeft - srcWidth / 2) * mult + srcWidth / 2; [dstWidth, dstHeight] = [dstHeight, dstWidth]; if (n < 0) { dstTop -= dstHeight; } else { dstLeft -= dstWidth; } srcLeft = dstLeft; srcTop = dstTop; [srcWidth, srcHeight] = [srcHeight, srcWidth]; } } dstImage.sourceLeft = dstLeft; dstImage.sourceTop = dstTop; dstImage.sourceWidth = dstWidth; dstImage.sourceHeight = dstHeight; } _extractNinePatchSliceDataFromImage() { const width = this._domImage.width; const height = this._domImage.height; if (!this._workingCanvas) { const engine = this._host?.getScene()?.getEngine() || EngineStore.LastCreatedEngine; if (!engine) { throw new Error("Invalid engine. Unable to create a canvas."); } this._workingCanvas = engine.createCanvas(width, height); } const canvas = this._workingCanvas; const context = canvas.getContext("2d"); context.drawImage(this._domImage, 0, 0, width, height); const imageData = context.getImageData(0, 0, width, height); // Left and right this._sliceLeft = -1; this._sliceRight = -1; for (let x = 0; x < width; x++) { const alpha = imageData.data[x * 4 + 3]; if (alpha > 127 && this._sliceLeft === -1) { this._sliceLeft = x; continue; } if (alpha < 127 && this._sliceLeft > -1) { this._sliceRight = x; break; } } // top and bottom this._sliceTop = -1; this._sliceBottom = -1; for (let y = 0; y < height; y++) { const alpha = imageData.data[y * width * 4 + 3]; if (alpha > 127 && this._sliceTop === -1) { this._sliceTop = y; continue; } if (alpha < 127 && this._sliceTop > -1) { this._sliceBottom = y; break; } } } /** * Gets or sets the internal DOM image used to render the control */ set domImage(value) { this._domImage = value; this._loaded = false; this._imageDataCache.data = null; if (this._domImage.width) { this._onImageLoaded(); } else { this._domImage.onload = () => { this._onImageLoaded(); }; } } get domImage() { return this._domImage; } _onImageLoaded() { this._imageDataCache.data = null; this._imageWidth = this._domImage.width; this._imageHeight = this._domImage.height; this._loaded = true; if (this._populateNinePatchSlicesFromImage) { this._extractNinePatchSliceDataFromImage(); } if (this._autoScale) { this.synchronizeSizeWithContent(); } this.onImageLoadedObservable.notifyObservers(this); this._markAsDirty(); } /** * Gets the image source url */ get source() { return this._source; } /** * Resets the internal Image Element cache. Can reduce memory usage. */ static ResetImageCache() { Image.SourceImgCache.clear(); } _removeCacheUsage(source) { const value = source && Image.SourceImgCache.get(source); if (value) { value.timesUsed -= 1; // Remove from DOM const htmlElement = value.img; if (htmlElement.parentNode) { htmlElement.parentNode.removeChild(htmlElement); } // Since the image isn't being used anymore, we can clean it from the cache if (value.timesUsed === 0) { Image.SourceImgCache.delete(source); } } } /** * Gets or sets image source url */ set source(value) { if (this._urlRewriter && value) { value = this._urlRewriter(value); } if (this._source === value) { return; } this._removeCacheUsage(this._source); this._loaded = false; this._source = value; this._imageDataCache.data = null; if (value) { value = this._svgCheck(value); } // Should abstract platform instead of using LastCreatedEngine const engine = this._host?.getScene()?.getEngine() || EngineStore.LastCreatedEngine; // If no engine, skip all other DOM operations. if (!engine) { return; } if (value && Image.SourceImgCache.has(value)) { const cachedData = Image.SourceImgCache.get(value); this._domImage = cachedData.img; cachedData.timesUsed += 1; if (cachedData.loaded) { this._onImageLoaded(); } else { cachedData.waitingForLoadCallback.push(this._onImageLoaded.bind(this)); } return; } this._domImage = engine.createCanvasImage(); // need to add to enforce rendering const imgElement = this._domImage; let addedToDom = false; if (imgElement.style && this._source?.endsWith(".svg")) { imgElement.style.visibility = "hidden"; imgElement.style.position = "absolute"; imgElement.style.top = "0"; engine.getRenderingCanvas()?.parentNode?.appendChild(imgElement); addedToDom = true; } if (value) { Image.SourceImgCache.set(value, { img: this._domImage, timesUsed: 1, loaded: false, waitingForLoadCallback: [this._onImageLoaded.bind(this)] }); } this._domImage.onload = () => { if (value) { const cachedData = Image.SourceImgCache.get(value); if (cachedData) { cachedData.loaded = true; for (const waitingCallback of cachedData.waitingForLoadCallback) { waitingCallback(); } cachedData.waitingForLoadCallback.length = 0; addedToDom && imgElement.remove(); return; } } this._onImageLoaded(); addedToDom && imgElement.remove(); }; if (value) { Tools.SetCorsBehavior(value, this._domImage); Tools.SetReferrerPolicyBehavior(this.referrerPolicy, this._domImage); this._domImage.src = value; } } _sanitizeSVG(svgString) { if (svgString.indexOf("<svg") === -1) { return svgString; // Not an SVG, return as is } const parser = new DOMParser(); const doc = parser.parseFromString(svgString, "image/svg+xml"); const dangerousTags = ["script", "iframe", "foreignObject", "object", "embed", "link", "style"]; const dangerousAttrs = [/^on/i, /^xlink:href$/, /^href$/]; // Remove dangerous elements dangerousTags.forEach((tag) => { const elements = doc.getElementsByTagName(tag); for (let i = elements.length - 1; i >= 0; i--) { elements[i].remove(); } }); // Recursively sanitize attributes function sanitizeElement(el) { if (el.attributes) { for (let i = el.attributes.length - 1; i >= 0; i--) { const attr = el.attributes[i]; const name = attr.name; const value = attr.value; // Remove dangerous attributes if (dangerousAttrs.some((regex) => regex.test(name))) { el.removeAttribute(name); } // Remove javascript: links if (typeof value === "string" && value.trim().toLowerCase().startsWith("javascript:")) { el.removeAttribute(name); } } } // Recursively sanitize children for (let i = 0; i < el.children.length; i++) { sanitizeElement(el.children[i]); } } sanitizeElement(doc.documentElement); return new XMLSerializer().serializeToString(doc); } /** * Checks for svg document with icon id present * @param value the source svg * @returns the svg */ _svgCheck(value) { // Skip SVG processing if no window/document or SVG support if (typeof window === "undefined" || typeof document === "undefined" || !window.SVGSVGElement) { return value; } if (window.SVGSVGElement && value.search(/(\.svg|\.svg?[?|#].*)$/gi) !== -1 && value.indexOf("#") === value.lastIndexOf("#")) { this._isSVG = true; value = this._sanitizeSVG(value); const svgsrc = value.split("#")[0]; const elemid = value.split("#")[1]; // check if object alr exist in document const svgExist = document.body.querySelector('object[data="' + svgsrc + '"]'); if (svgExist) { const svgDoc = svgExist.contentDocument; // get viewbox width and height, get svg document width and height in px if (svgDoc && svgDoc.documentElement) { const vb = svgDoc.documentElement.getAttribute("viewBox"); const docwidth = Number(svgDoc.documentElement.getAttribute("width")); const docheight = Number(svgDoc.documentElement.getAttribute("height")); const elem = svgDoc.getElementById(elemid); if (elem && vb && docwidth && docheight) { this._getSVGAttribs(svgExist, elemid); return value; } } // wait for object to load svgExist.addEventListener("load", () => { this._getSVGAttribs(svgExist, elemid); }); } else { // create document object const svgImage = document.createElement("object"); svgImage.data = svgsrc; svgImage.type = "image/svg+xml"; svgImage.width = "0%"; svgImage.height = "0%"; document.body.appendChild(svgImage); // when the object has loaded, get the element attribs svgImage.onload = () => { const svgobj = document.body.querySelector('object[data="' + svgsrc + '"]'); if (svgobj) { this._getSVGAttribs(svgobj, elemid); } }; } return svgsrc; } else { return value; } } /** * Sets sourceLeft, sourceTop, sourceWidth, sourceHeight automatically * given external svg file and icon id * @param svgsrc * @param elemid */ _getSVGAttribs(svgsrc, elemid) { const svgDoc = svgsrc.contentDocument; // get viewbox width and height, get svg document width and height in px if (svgDoc && svgDoc.documentElement) { const vb = svgDoc.documentElement.getAttribute("viewBox"); const docwidth = Number(svgDoc.documentElement.getAttribute("width")); const docheight = Number(svgDoc.documentElement.getAttribute("height")); // get element bbox and matrix transform const elem = svgDoc.getElementById(elemid); if (vb && docwidth && docheight && elem) { const vbWidth = Number(vb.split(" ")[2]); const vbHeight = Number(vb.split(" ")[3]); const elemBbox = elem.getBBox(); let elemMatrixA = 1; let elemMatrixD = 1; let elemMatrixE = 0; let elemMatrixF = 0; const mainMatrix = elem.transform.baseVal.consolidate().matrix; if (elem.transform && elem.transform.baseVal.consolidate()) { elemMatrixA = mainMatrix.a; elemMatrixD = mainMatrix.d; elemMatrixE = mainMatrix.e; elemMatrixF = mainMatrix.f; } // compute source coordinates and dimensions this.sourceLeft = ((elemMatrixA * elemBbox.x + elemMatrixE) * docwidth) / vbWidth; this.sourceTop = ((elemMatrixD * elemBbox.y + elemMatrixF) * docheight) / vbHeight; this.sourceWidth = elemBbox.width * elemMatrixA * (docwidth / vbWidth); this.sourceHeight = elemBbox.height * elemMatrixD * (docheight / vbHeight); this._svgAttributesComputationCompleted = true; this.onSVGAttributesComputedObservable.notifyObservers(this); } } } /** * Gets or sets the cell width to use when animation sheet is enabled * @see https://doc.babylonjs.com/features/featuresDeepDive/gui/gui#image */ get cellWidth() { return this._cellWidth; } set cellWidth(value) { if (this._cellWidth === value) { return; } this._cellWidth = value; this._markAsDirty(); } /** * Gets or sets the cell height to use when animation sheet is enabled * @see https://doc.babylonjs.com/features/featuresDeepDive/gui/gui#image */ get cellHeight() { return this._cellHeight; } set cellHeight(value) { if (this._cellHeight === value) { return; } this._cellHeight = value; this._markAsDirty(); } /** * Gets or sets the cell id to use (this will turn on the animation sheet mode) * @see https://doc.babylonjs.com/features/featuresDeepDive/gui/gui#image */ get cellId() { return this._cellId; } set cellId(value) { if (this._cellId === value) { return; } this._cellId = value; this._markAsDirty(); } /** * Creates a new Image * @param name defines the control name * @param url defines the image url */ constructor(name, url = null) { super(name); this.name = name; this._workingCanvas = null; this._loaded = false; this._stretch = Image.STRETCH_FILL; this._source = null; this._autoScale = false; this._sourceLeft = 0; this._sourceTop = 0; this._sourceWidth = 0; this._sourceHeight = 0; this._svgAttributesComputationCompleted = false; this._isSVG = false; this._cellWidth = 0; this._cellHeight = 0; this._cellId = -1; this._populateNinePatchSlicesFromImage = false; this._imageDataCache = { data: null, key: "" }; /** * Observable notified when the content is loaded */ this.onImageLoadedObservable = new Observable(); /** * Observable notified when _sourceLeft, _sourceTop, _sourceWidth and _sourceHeight are computed */ this.onSVGAttributesComputedObservable = new Observable(); this.source = url; } /** * Tests if a given coordinates belong to the current control * @param x defines x coordinate to test * @param y defines y coordinate to test * @returns true if the coordinates are inside the control */ contains(x, y) { if (!super.contains(x, y)) { return false; } if (!this._detectPointerOnOpaqueOnly || !this._workingCanvas) { return true; } const width = this._currentMeasure.width | 0; const height = this._currentMeasure.height | 0; const key = width + "_" + height; let imageData = this._imageDataCache.data; if (!imageData || this._imageDataCache.key !== key) { const canvas = this._workingCanvas; const context = canvas.getContext("2d"); this._imageDataCache.data = imageData = context.getImageData(0, 0, width, height).data; this._imageDataCache.key = key; } x = (x - this._currentMeasure.left) | 0; y = (y - this._currentMeasure.top) | 0; const pickedPixel = imageData[(x + y * width) * 4 + 3]; return pickedPixel > 0; } _getTypeName() { return "Image"; } /** Force the control to synchronize with its content */ synchronizeSizeWithContent() { if (!this._loaded) { return; } this.width = this._domImage.width + "px"; this.height = this._domImage.height + "px"; } _processMeasures(parentMeasure, context) { if (this._loaded) { switch (this._stretch) { case Image.STRETCH_NONE: break; case Image.STRETCH_FILL: break; case Image.STRETCH_UNIFORM: break; case Image.STRETCH_NINE_PATCH: break; case Image.STRETCH_EXTEND: if (this._autoScale) { this.synchronizeSizeWithContent(); } if (this.parent && this.parent.parent) { // Will update root size if root is not the top root this.parent.adaptWidthToChildren = true; this.parent.adaptHeightToChildren = true; } break; } } super._processMeasures(parentMeasure, context); } _prepareWorkingCanvasForOpaqueDetection() { if (!this._detectPointerOnOpaqueOnly) { return; } const width = this._currentMeasure.width; const height = this._currentMeasure.height; if (!this._workingCanvas) { const engine = this._host?.getScene()?.getEngine() || EngineStore.LastCreatedEngine; if (!engine) { throw new Error("Invalid engine. Unable to create a canvas."); } this._workingCanvas = engine.createCanvas(width, height); } const canvas = this._workingCanvas; const context = canvas.getContext("2d"); context.clearRect(0, 0, width, height); } _drawImage(context, sx, sy, sw, sh, tx, ty, tw, th) { context.drawImage(this._domImage, sx, sy, sw, sh, tx, ty, tw, th); if (!this._detectPointerOnOpaqueOnly) { return; } const transform = context.getTransform(); const canvas = this._workingCanvas; const workingCanvasContext = canvas.getContext("2d"); workingCanvasContext.save(); const ttx = tx - this._currentMeasure.left; const tty = ty - this._currentMeasure.top; workingCanvasContext.setTransform(transform.a, transform.b, transform.c, transform.d, (ttx + tw) / 2, (tty + th) / 2); workingCanvasContext.translate(-(ttx + tw) / 2, -(tty + th) / 2); workingCanvasContext.drawImage(this._domImage, sx, sy, sw, sh, ttx, tty, tw, th); workingCanvasContext.restore(); } _draw(context) { context.save(); if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) { context.shadowColor = this.shadowColor; context.shadowBlur = this.shadowBlur; context.shadowOffsetX = this.shadowOffsetX; context.shadowOffsetY = this.shadowOffsetY; } let x, y, width, height; if (this.cellId == -1) { x = this._sourceLeft; y = this._sourceTop; width = this._sourceWidth ? this._sourceWidth : this._imageWidth; height = this._sourceHeight ? this._sourceHeight : this._imageHeight; } else { const rowCount = this._domImage.naturalWidth / this.cellWidth; const column = (this.cellId / rowCount) >> 0; const row = this.cellId % rowCount; x = this.cellWidth * row; y = this.cellHeight * column; width = this.cellWidth; height = this.cellHeight; } this._prepareWorkingCanvasForOpaqueDetection(); this._applyStates(context); if (this._loaded) { switch (this._stretch) { case Image.STRETCH_NONE: this._drawImage(context, x, y, width, height, this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height); break; case Image.STRETCH_FILL: this._drawImage(context, x, y, width, height, this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height); break; case Image.STRETCH_UNIFORM: { const hRatio = this._currentMeasure.width / width; const vRatio = this._currentMeasure.height / height; const ratio = Math.min(hRatio, vRatio); const centerX = (this._currentMeasure.width - width * ratio) / 2; const centerY = (this._currentMeasure.height - height * ratio) / 2; this._drawImage(context, x, y, width, height, this._currentMeasure.left + centerX, this._currentMeasure.top + centerY, width * ratio, height * ratio); break; } case Image.STRETCH_EXTEND: this._drawImage(context, x, y, width, height, this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height); break; case Image.STRETCH_NINE_PATCH: this._renderNinePatch(context, x, y, width, height); break; } } context.restore(); } _renderNinePatch(context, sx, sy, sw, sh) { const idealRatio = this.host.idealRatio; const leftWidth = this._sliceLeft; const topHeight = this._sliceTop; const bottomHeight = sh - this._sliceBottom; const rightWidth = sw - this._sliceRight; const centerWidth = this._sliceRight - this._sliceLeft; const centerHeight = this._sliceBottom - this._sliceTop; const leftWidthAdjusted = Math.round(leftWidth * idealRatio); const topHeightAdjusted = Math.round(topHeight * idealRatio); const bottomHeightAdjusted = Math.round(bottomHeight * idealRatio); const rightWidthAdjusted = Math.round(rightWidth * idealRatio); const targetCenterWidth = Math.round(this._currentMeasure.width) - rightWidthAdjusted - leftWidthAdjusted + 2; const targetCenterHeight = Math.round(this._currentMeasure.height) - bottomHeightAdjusted - topHeightAdjusted + 2; const centerLeftOffset = Math.round(this._currentMeasure.left) + leftWidthAdjusted - 1; const centerTopOffset = Math.round(this._currentMeasure.top) + topHeightAdjusted - 1; const rightOffset = Math.round(this._currentMeasure.left + this._currentMeasure.width) - rightWidthAdjusted; const bottomOffset = Math.round(this._currentMeasure.top + this._currentMeasure.height) - bottomHeightAdjusted; //Top Left this._drawImage(context, sx, sy, leftWidth, topHeight, this._currentMeasure.left, this._currentMeasure.top, leftWidthAdjusted, topHeightAdjusted); //Top this._drawImage(context, sx + this._sliceLeft, sy, centerWidth, topHeight, centerLeftOffset + 1, this._currentMeasure.top, targetCenterWidth - 2, topHeightAdjusted); //Top Right this._drawImage(context, sx + this._sliceRight, sy, rightWidth, topHeight, rightOffset, this._currentMeasure.top, rightWidthAdjusted, topHeightAdjusted); //Left this._drawImage(context, sx, sy + this._sliceTop, leftWidth, centerHeight, this._currentMeasure.left, centerTopOffset + 1, leftWidthAdjusted, targetCenterHeight - 2); // Center this._drawImage(context, sx + this._sliceLeft, sy + this._sliceTop, centerWidth, centerHeight, centerLeftOffset + 1, centerTopOffset + 1, targetCenterWidth - 2, targetCenterHeight - 2); //Right this._drawImage(context, sx + this._sliceRight, sy + this._sliceTop, rightWidth, centerHeight, rightOffset, centerTopOffset + 1, rightWidthAdjusted, targetCenterHeight - 2); //Bottom Left this._drawImage(context, sx, sy + this._sliceBottom, leftWidth, bottomHeight, this._currentMeasure.left, bottomOffset, leftWidthAdjusted, bottomHeightAdjusted); //Bottom this._drawImage(context, sx + this.sliceLeft, sy + this._sliceBottom, centerWidth, bottomHeight, centerLeftOffset + 1, bottomOffset, targetCenterWidth - 2, bottomHeightAdjusted); //Bottom Right this._drawImage(context, sx + this._sliceRight, sy + this._sliceBottom, rightWidth, bottomHeight, rightOffset, bottomOffset, rightWidthAdjusted, bottomHeightAdjusted); } dispose() { super.dispose(); this.onImageLoadedObservable.clear(); this.onSVGAttributesComputedObservable.clear(); this._removeCacheUsage(this._source); } } /** * Cache of images to avoid loading the same image multiple times */ Image.SourceImgCache = new Map(); // Static /** STRETCH_NONE */ Image.STRETCH_NONE = 0; /** STRETCH_FILL */ Image.STRETCH_FILL = 1; /** STRETCH_UNIFORM */ Image.STRETCH_UNIFORM = 2; /** STRETCH_EXTEND */ Image.STRETCH_EXTEND = 3; /** NINE_PATCH */ Image.STRETCH_NINE_PATCH = 4; __decorate([ serialize() ], Image.prototype, "detectPointerOnOpaqueOnly", null); __decorate([ serialize() ], Image.prototype, "sliceLeft", null); __decorate([ serialize() ], Image.prototype, "sliceRight", null); __decorate([ serialize() ], Image.prototype, "sliceTop", null); __decorate([ serialize() ], Image.prototype, "sliceBottom", null); __decorate([ serialize() ], Image.prototype, "sourceLeft", null); __decorate([ serialize() ], Image.prototype, "sourceTop", null); __decorate([ serialize() ], Image.prototype, "sourceWidth", null); __decorate([ serialize() ], Image.prototype, "sourceHeight", null); __decorate([ serialize() ], Image.prototype, "populateNinePatchSlicesFromImage", null); __decorate([ serialize() ], Image.prototype, "autoScale", null); __decorate([ serialize() ], Image.prototype, "stretch", null); __decorate([ serialize() ], Image.prototype, "source", null); __decorate([ serialize() ], Image.prototype, "cellWidth", null); __decorate([ serialize() ], Image.prototype, "cellHeight", null); __decorate([ serialize() ], Image.prototype, "cellId", null); RegisterClass("BABYLON.GUI.Image", Image); //# sourceMappingURL=image.js.map