import { Nullable } from "babylonjs/types" ; import { Observable } from "babylonjs/Misc/observable" ; import { Tools } from "babylonjs/Misc/tools" ; import { Control } from "./control" ; import { Measure } from "../measure" ; import { RegisterClass } from "babylonjs/Misc/typeStore" ; import { serialize } from "babylonjs/Misc/decorators" ; import { Engine } from "babylonjs/Engines/engine" ; import { ICanvas , ICanvasRenderingContext , IImage } from "babylonjs/Engines/ICanvas" ; /**
 * Class used to create 2D images
 */ export class Image extends Control { private _workingCanvas : Nullable < ICanvas > = null ; private _domImage : IImage ; private _imageWidth : number ; private _imageHeight : number ; private _loaded = false ; private _stretch = Image . STRETCH_FILL ; private _source : Nullable < string > ; private _autoScale = false ; private _sourceLeft = 0 ; private _sourceTop = 0 ; private _sourceWidth = 0 ; private _sourceHeight = 0 ; private _svgAttributesComputationCompleted : boolean = false ; private _isSVG : boolean = false ; private _cellWidth : number = 0 ; private _cellHeight : number = 0 ; private _cellId : number = - 1 ; private _sliceLeft : number ; private _sliceRight : number ; private _sliceTop : number ; private _sliceBottom : number ; private _populateNinePatchSlicesFromImage = false ; private _detectPointerOnOpaqueOnly : boolean ; private _imageDataCache : { data : Uint8ClampedArray | null ; key : string ; } = { data : null , key : "" } ; /**
     * Observable notified when the content is loaded
     */ public onImageLoadedObservable = new Observable < Image > ( ) ; /**
     * Observable notified when _sourceLeft, _sourceTop, _sourceWidth and _sourceHeight are computed
     */ public onSVGAttributesComputedObservable = new Observable < Image > ( ) ; /**
     * Gets a boolean indicating that the content is loaded
     */ public get isLoaded ( ) : boolean { return this . _loaded ; } /**
     * Gets or sets a boolean indicating if pointers should only be validated on pixels with alpha > 0.
     * Beware using this as this will comsume more memory as the image has to be stored twice
     */ @ serialize ( ) public get detectPointerOnOpaqueOnly ( ) : boolean { return this . _detectPointerOnOpaqueOnly ; } public set detectPointerOnOpaqueOnly ( value ) { if ( this . _detectPointerOnOpaqueOnly === value ) { return ; } this . _detectPointerOnOpaqueOnly = value ; } /**
     * Gets or sets the left value for slicing (9-patch)
     */ @ serialize ( ) public get sliceLeft ( ) : number { return this . _sliceLeft ; } public set sliceLeft ( value ) { if ( this . _sliceLeft === value ) { return ; } this . _sliceLeft = value ; this . _markAsDirty ( ) ; } /**
     * Gets or sets the right value for slicing (9-patch)
     */ @ serialize ( ) public get sliceRight ( ) : number { return this . _sliceRight ; } public set sliceRight ( value ) { if ( this . _sliceRight === value ) { return ; } this . _sliceRight = value ; this . _markAsDirty ( ) ; } /**
     * Gets or sets the top value for slicing (9-patch)
     */ @ serialize ( ) public get sliceTop ( ) : number { return this . _sliceTop ; } public set sliceTop ( value ) { if ( this . _sliceTop === value ) { return ; } this . _sliceTop = value ; this . _markAsDirty ( ) ; } /**
     * Gets or sets the bottom value for slicing (9-patch)
     */ @ serialize ( ) public get sliceBottom ( ) : number { return this . _sliceBottom ; } public set sliceBottom ( value ) { if ( this . _sliceBottom === value ) { return ; } this . _sliceBottom = value ; this . _markAsDirty ( ) ; } /**
     * Gets or sets the left coordinate in the source image
     */ @ serialize ( ) public get sourceLeft ( ) : number { return this . _sourceLeft ; } public set sourceLeft ( value ) { if ( this . _sourceLeft === value ) { return ; } this . _sourceLeft = value ; this . _markAsDirty ( ) ; } /**
     * Gets or sets the top coordinate in the source image
     */ @ serialize ( ) public get sourceTop ( ) : number { return this . _sourceTop ; } public set sourceTop ( value ) { if ( this . _sourceTop === value ) { return ; } this . _sourceTop = value ; this . _markAsDirty ( ) ; } /**
     * Gets or sets the width to capture in the source image
     */ @ serialize ( ) public get sourceWidth ( ) : number { return this . _sourceWidth ; } public set sourceWidth ( value ) { if ( this . _sourceWidth === value ) { return ; } this . _sourceWidth = value ; this . _markAsDirty ( ) ; } /**
     * Gets or sets the height to capture in the source image
     */ @ serialize ( ) public get sourceHeight ( ) : number { return this . _sourceHeight ; } public set sourceHeight ( value ) { if ( this . _sourceHeight === value ) { return ; } this . _sourceHeight = value ; this . _markAsDirty ( ) ; } /**
     * Gets the image width
     */ public get imageWidth ( ) : number { return this . _imageWidth ; } /**
     * Gets the image height
     */ public get imageHeight ( ) : number { return this . _imageHeight ; } /**
    * Gets or sets a boolean indicating if nine patch slices (left, top, right, bottom) should be read from image data
    */ @ serialize ( ) public get populateNinePatchSlicesFromImage ( ) : boolean { return this . _populateNinePatchSlicesFromImage ; } public 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 */ public get isSVG ( ) : boolean { return this . _isSVG ; } /** Gets the status of the SVG attributes computation (sourceLeft, sourceTop, sourceWidth, sourceHeight) */ public get svgAttributesComputationCompleted ( ) : boolean { return this . _svgAttributesComputationCompleted ; } see https @ serialize ( ) public get autoScale ( ) : boolean { return this . _autoScale ; } public set autoScale ( value ) { if ( this . _autoScale === value ) { return ; } this . _autoScale = value ; if ( value && this . _loaded ) { this . synchronizeSizeWithContent ( ) ; } } /** Gets or sets the streching mode used by the image */ @ serialize ( ) public get stretch ( ) : number { return this . _stretch ; } public set stretch ( value ) { if ( this . _stretch === value ) { return ; } this . _stretch = value ; this . _markAsDirty ( ) ; } hidden public _rotate90 ( n , preserveProperties = false ) { const width = this . _domImage . width ; const height = this . _domImage . height ; const engine = this . _host ?. getScene ( ) ?. getEngine ( ) || Engine . LastCreatedEngine ; if ( ! engine ) { throw new Error ( "Invalid engine. Unable to create a canvas." ) ; } let 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 ; } private _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 ( ) ; } ) ; } } private _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 ) { let 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 ; } private _extractNinePatchSliceDataFromImage ( ) { const width = this . _domImage . width ; const height = this . _domImage . height ; if ( ! this . _workingCanvas ) { const engine = this . _host ?. getScene ( ) ?. getEngine ( ) || Engine . 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 ) ; this . _sliceLeft = - 1 ; this . _sliceRight = - 1 ; for ( var 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 ; } } this . _sliceTop = - 1 ; this . _sliceBottom = - 1 ; for ( var 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
     */ public 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 ( ) ; } ; } } public get domImage ( ) : IImage { return this . _domImage ; } private _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
     */ @ serialize ( ) public get source ( ) { return this . _source ; } /**
     * Gets or sets image source url
     */ public set source ( value ) { if ( this . _source === value ) { return ; } this . _loaded = false ; this . _source = value ; this . _imageDataCache . data = null ; if ( value ) { value = this . _svgCheck ( value ) ; } const engine = this . _host ?. getScene ( ) ?. getEngine ( ) || Engine . LastCreatedEngine ; if ( ! engine ) { throw new Error ( "Invalid engine. Unable to create a canvas." ) ; } this . _domImage = engine . createCanvasImage ( ) ; this . _domImage . onload = ( ) => { this . _onImageLoaded ( ) ; } ; if ( value ) { Tools . SetCorsBehavior ( value , this . _domImage ) ; this . _domImage . src = value ; } } /**
     * Checks for svg document with icon id present
     */ private _svgCheck ( value ) { if ( window . SVGSVGElement && value . search ( /.svg#/gi ) !== - 1 && value . indexOf ( "#" ) === value . lastIndexOf ( "#" ) ) { this . _isSVG = true ; var svgsrc = value . split ( "#" ) [ 0 ] ; var elemid = value . split ( "#" ) [ 1 ] ; var svgExist = < HTMLObjectElement > document . body . querySelector ( 'object[data="' + svgsrc + '"]' ) ; if ( svgExist ) { var svgDoc = svgExist . contentDocument ; if ( svgDoc && svgDoc . documentElement ) { var vb = svgDoc . documentElement . getAttribute ( "viewBox" ) ; var docwidth = Number ( svgDoc . documentElement . getAttribute ( "width" ) ) ; var docheight = Number ( svgDoc . documentElement . getAttribute ( "height" ) ) ; var elem = < SVGGraphicsElement > ( < unknown > svgDoc . getElementById ( elemid ) ) ; if ( elem && vb && docwidth && docheight ) { this . _getSVGAttribs ( svgExist , elemid ) ; return value ; } } svgExist . addEventListener ( "load" , ( ) => { this . _getSVGAttribs ( svgExist , elemid ) ; } ) ; } else { var svgImage = document . createElement ( "object" ) ; svgImage . data = svgsrc ; svgImage . type = "image/svg+xml" ; svgImage . width = "0%" ; svgImage . height = "0%" ; document . body . appendChild ( svgImage ) ; svgImage . onload = ( ) => { var svgobj = < HTMLObjectElement > 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
     */ private _getSVGAttribs ( svgsrc , elemid ) { var svgDoc = svgsrc . contentDocument ; if ( svgDoc && svgDoc . documentElement ) { var vb = svgDoc . documentElement . getAttribute ( "viewBox" ) ; var docwidth = Number ( svgDoc . documentElement . getAttribute ( "width" ) ) ; var docheight = Number ( svgDoc . documentElement . getAttribute ( "height" ) ) ; var elem = svgDoc . getElementById ( elemid ) as Nullable < SVGGraphicsElement > ; if ( vb && docwidth && docheight && elem ) { var vb_width = Number ( vb . split ( " " ) [ 2 ] ) ; var vb_height = Number ( vb . split ( " " ) [ 3 ] ) ; var elem_bbox = elem . getBBox ( ) ; var elem_matrix_a = 1 ; var elem_matrix_d = 1 ; var elem_matrix_e = 0 ; var elem_matrix_f = 0 ; const mainMatrix = elem . transform . baseVal . consolidate ( ) ! . matrix ; if ( elem . transform && elem . transform . baseVal . consolidate ( ) ) { elem_matrix_a = mainMatrix . a ; elem_matrix_d = mainMatrix . d ; elem_matrix_e = mainMatrix . e ; elem_matrix_f = mainMatrix . f ; } this . sourceLeft = ( ( elem_matrix_a * elem_bbox . x + elem_matrix_e ) * docwidth ) / vb_width ; this . sourceTop = ( ( elem_matrix_d * elem_bbox . y + elem_matrix_f ) * docheight ) / vb_height ; this . sourceWidth = elem_bbox . width * elem_matrix_a * ( docwidth / vb_width ) ; this . sourceHeight = elem_bbox . height * elem_matrix_d * ( docheight / vb_height ) ; this . _svgAttributesComputationCompleted = true ; this . onSVGAttributesComputedObservable . notifyObservers ( this ) ; } } } see https @ serialize ( ) get cellWidth ( ) : number { return this . _cellWidth ; } set cellWidth ( value ) { if ( this . _cellWidth === value ) { return ; } this . _cellWidth = value ; this . _markAsDirty ( ) ; } see https @ serialize ( ) get cellHeight ( ) : number { return this . _cellHeight ; } set cellHeight ( value ) { if ( this . _cellHeight === value ) { return ; } this . _cellHeight = value ; this . _markAsDirty ( ) ; } see https @ serialize ( ) get cellId ( ) : number { return this . _cellId ; } set cellId ( value ) { if ( this . _cellId === value ) { return ; } this . _cellId = value ; this . _markAsDirty ( ) ; } param name param url constructor ( public name ? , url = null ) { super ( name ) ; this . source = url ; } param x param y returns public 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 ; } protected _getTypeName ( ) { return "Image" ; } /** Force the control to synchronize with its content */ public synchronizeSizeWithContent ( ) { if ( ! this . _loaded ) { return ; } this . width = this . _domImage . width + "px" ; this . height = this . _domImage . height + "px" ; } protected _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 ) { this . parent . adaptWidthToChildren = true ; this . parent . adaptHeightToChildren = true ; } break ; } } super . _processMeasures ( parentMeasure , context ) ; } private _prepareWorkingCanvasForOpaqueDetection ( ) { if ( ! this . _detectPointerOnOpaqueOnly ) { return ; } const width = this . _currentMeasure . width ; const height = this . _currentMeasure . height ; if ( ! this . _workingCanvas ) { const engine = this . _host ?. getScene ( ) ?. getEngine ( ) || Engine . 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 ) ; } private _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 canvas = this . _workingCanvas ! ; context = canvas . getContext ( "2d" ) ! ; context . drawImage ( this . _domImage , sx , sy , sw , sh , tx - this . _currentMeasure . left , ty - this . _currentMeasure . top , tw , th ) ; } public _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 { let rowCount = this . _domImage . naturalWidth / this . cellWidth ; let column = ( this . cellId / rowCount ) >> 0 ; let 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 : var hRatio = this . _currentMeasure . width / width ; var vRatio = this . _currentMeasure . height / height ; var ratio = Math . min ( hRatio , vRatio ) ; var centerX = ( this . _currentMeasure . width - width * ratio ) / 2 ; var 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 ) ; break ; } } context . restore ( ) ; } private _renderNinePatch ( context ) { const leftWidth = this . _sliceLeft ; const topHeight = this . _sliceTop ; const bottomHeight = this . _imageHeight - this . _sliceBottom ; const rightWidth = this . _imageWidth - this . _sliceRight ; const centerWidth = this . _sliceRight - this . _sliceLeft ; const centerHeight = this . _sliceBottom - this . _sliceTop ; const targetCenterWidth = ( this . _currentMeasure . width - rightWidth - leftWidth ) + 2 ; const targetCenterHeight = ( this . _currentMeasure . height - bottomHeight - topHeight ) + 2 ; const centerLeftOffset = this . _currentMeasure . left + leftWidth - 1 ; const centerTopOffset = this . _currentMeasure . top + topHeight - 1 ; const rightOffset = this . _currentMeasure . left + this . _currentMeasure . width - rightWidth ; const bottomOffset = this . _currentMeasure . top + this . _currentMeasure . height - bottomHeight ; this . _drawImage ( context , 0 , 0 , leftWidth , topHeight , this . _currentMeasure . left , this . _currentMeasure . top , leftWidth , topHeight ) ; this . _drawImage ( context , this . _sliceLeft , 0 , centerWidth , topHeight , centerLeftOffset , this . _currentMeasure . top , targetCenterWidth , topHeight ) ; this . _drawImage ( context , this . sliceRight , 0 , rightWidth , topHeight , rightOffset , this . _currentMeasure . top , rightWidth , topHeight ) ; this . _drawImage ( context , 0 , this . _sliceTop , leftWidth , centerHeight , this . _currentMeasure . left , centerTopOffset , leftWidth , targetCenterHeight ) ; this . _drawImage ( context , this . _sliceLeft , this . _sliceTop , centerWidth , centerHeight , centerLeftOffset , centerTopOffset , targetCenterWidth , targetCenterHeight ) ; this . _drawImage ( context , this . _sliceRight , this . _sliceTop , rightWidth , centerHeight , rightOffset , centerTopOffset , rightWidth , targetCenterHeight ) ; this . _drawImage ( context , 0 , this . _sliceBottom , leftWidth , bottomHeight , this . _currentMeasure . left , bottomOffset , leftWidth , bottomHeight ) ; this . _drawImage ( context , this . sliceLeft , this . _sliceBottom , centerWidth , bottomHeight , centerLeftOffset , bottomOffset , targetCenterWidth , bottomHeight ) ; this . _drawImage ( context , this . _sliceRight , this . _sliceBottom , rightWidth , bottomHeight , rightOffset , bottomOffset , rightWidth , bottomHeight ) ; } public dispose ( ) { super . dispose ( ) ; this . onImageLoadedObservable . clear ( ) ; this . onSVGAttributesComputedObservable . clear ( ) ; } /** STRETCH_NONE */ public static readonly STRETCH_NONE = 0 ; /** STRETCH_FILL */ public static readonly STRETCH_FILL = 1 ; /** STRETCH_UNIFORM */ public static readonly STRETCH_UNIFORM = 2 ; /** STRETCH_EXTEND */ public static readonly STRETCH_EXTEND = 3 ; /** NINE_PATCH */ public static readonly STRETCH_NINE_PATCH = 4 ; } RegisterClass ( "BABYLON.GUI.Image" , Image ) ;