From 5af738593e0ffb6b0e0537b8f847706dee827cdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Sun, 22 Sep 2013 09:58:05 +0100 Subject: [PATCH] Image source refactoring --- src/objectliterals.jsdoc | 19 ++- src/ol/canvasfunction.js | 8 ++ src/ol/image.js | 100 ++------------ src/ol/imagebase.js | 127 ++++++++++++++++++ src/ol/imagecanvas.js | 38 ++++++ .../canvas/canvasimagelayerrenderer.js | 4 +- src/ol/renderer/dom/domimagelayerrenderer.js | 4 +- .../renderer/webgl/webglimagelayerrenderer.js | 6 +- src/ol/source/imagecanvassource.exports | 1 + src/ol/source/imagecanvassource.js | 76 +++++++++++ src/ol/source/imagesource.js | 47 +------ src/ol/source/imagestaticsource.js | 38 ++---- src/ol/source/imagewmssource.js | 13 +- src/ol/source/mapguidesource.js | 52 ++++--- 14 files changed, 343 insertions(+), 190 deletions(-) create mode 100644 src/ol/canvasfunction.js create mode 100644 src/ol/imagebase.js create mode 100644 src/ol/imagecanvas.js create mode 100644 src/ol/source/imagecanvassource.exports create mode 100644 src/ol/source/imagecanvassource.js diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index fb1be4393a..1932785769 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -623,6 +623,20 @@ * @todo stability experimental */ +/** + * @typedef {Object} olx.source.ImageCanvasOptions + * @property {Array.|undefined} attributions Attributions. + * @property {ol.CanvasFunctionType} canvasFunction Canvas function. + * @property {ol.Extent|undefined} extent Extent. + * @property {string|undefined} logo Logo. + * @property {ol.proj.ProjectionLike} projection Projection. + * @property {number|undefined} ratio Ratio. 1 means canvases are the size + * of the map viewport, 2 means twice the size of the map viewport, and so + * on. + * @property {Array.|undefined} resolutions Resolutions. If specified, + * new canvases will be created for these resolutions only. + */ + /** * @typedef {Object} olx.source.ImageWMSOptions * @property {Array.|undefined} attributions Attributions. @@ -634,6 +648,7 @@ * @property {ol.source.wms.ServerType|undefined} serverType The type of the remote WMS * server: `mapserver`, `geoserver` or `qgis`. Only needed if `hidpi` is `true`. * Default is `undefined`. + * @property {string|undefined} logo Logo. * @property {Object.} params WMS request parameters. At least a * `LAYERS` param is required. `STYLES` is `` by default. `VERSION` is * `1.3.0` by default. `WIDTH`, `HEIGHT`, `BBOX` and `CRS` (`SRS` for WMS @@ -669,9 +684,9 @@ * @property {ol.Extent|undefined} extent Extent. * @property {ol.Extent|undefined} imageExtent Extent of the image. * @property {ol.Size|undefined} imageSize Size of the image. + * @property {string|undefined} logo Logo. * @property {ol.proj.ProjectionLike} projection Projection. - * @property {string|undefined} url URL. - * @todo stability experimental + * @property {string} url Url. */ /** diff --git a/src/ol/canvasfunction.js b/src/ol/canvasfunction.js new file mode 100644 index 0000000000..878d79645c --- /dev/null +++ b/src/ol/canvasfunction.js @@ -0,0 +1,8 @@ +goog.provide('ol.CanvasFunctionType'); + + +/** + * @typedef {function(this:ol.source.ImageCanvas, ol.Extent, number, ol.Size, + * ol.proj.Projection): HTMLCanvasElement} + */ +ol.CanvasFunctionType; diff --git a/src/ol/image.js b/src/ol/image.js index ba5d6dcc52..963e4bdff8 100644 --- a/src/ol/image.js +++ b/src/ol/image.js @@ -1,54 +1,30 @@ goog.provide('ol.Image'); -goog.provide('ol.ImageState'); goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.events'); -goog.require('goog.events.EventTarget'); goog.require('goog.events.EventType'); goog.require('goog.object'); -goog.require('ol.Attribution'); -goog.require('ol.Extent'); - - -/** - * @enum {number} - */ -ol.ImageState = { - IDLE: 0, - LOADING: 1, - LOADED: 2, - ERROR: 3 -}; +goog.require('ol.ImageBase'); +goog.require('ol.ImageState'); /** * @constructor - * @extends {goog.events.EventTarget} + * @extends {ol.ImageBase} * @param {ol.Extent} extent Extent. * @param {number} resolution Resolution. * @param {number} pixelRatio Pixel ratio. + * @param {Array.} attributions Attributions. * @param {string} src Image source URI. * @param {?string} crossOrigin Cross origin. - * @param {Array.} attributions Attributions. */ ol.Image = - function(extent, resolution, pixelRatio, src, crossOrigin, attributions) { + function(extent, resolution, pixelRatio, attributions, src, crossOrigin) { - goog.base(this); - - /** - * @private - * @type {Array.} - */ - this.attributions_ = attributions; - - /** - * @private - * @type {ol.Extent} - */ - this.extent_ = extent; + goog.base(this, extent, resolution, pixelRatio, ol.ImageState.IDLE, + attributions); /** * @private @@ -56,18 +32,6 @@ ol.Image = */ this.src_ = src; - /** - * @private - * @type {number} - */ - this.pixelRatio_ = pixelRatio; - - /** - * @private - * @type {number} - */ - this.resolution_ = resolution; - /** * @private * @type {Image} @@ -95,31 +59,7 @@ ol.Image = */ this.state = ol.ImageState.IDLE; }; -goog.inherits(ol.Image, goog.events.EventTarget); - - -/** - * @protected - */ -ol.Image.prototype.dispatchChangeEvent = function() { - this.dispatchEvent(goog.events.EventType.CHANGE); -}; - - -/** - * @return {Array.} Attributions. - */ -ol.Image.prototype.getAttributions = function() { - return this.attributions_; -}; - - -/** - * @return {ol.Extent} Extent. - */ -ol.Image.prototype.getExtent = function() { - return this.extent_; -}; +goog.inherits(ol.Image, ol.ImageBase); /** @@ -145,30 +85,6 @@ ol.Image.prototype.getImageElement = function(opt_context) { }; -/** - * @return {number} PixelRatio. - */ -ol.Image.prototype.getPixelRatio = function() { - return this.pixelRatio_; -}; - - -/** - * @return {number} Resolution. - */ -ol.Image.prototype.getResolution = function() { - return this.resolution_; -}; - - -/** - * @return {ol.ImageState} State. - */ -ol.Image.prototype.getState = function() { - return this.state; -}; - - /** * Tracks loading or read errors. * diff --git a/src/ol/imagebase.js b/src/ol/imagebase.js new file mode 100644 index 0000000000..8924be9a5f --- /dev/null +++ b/src/ol/imagebase.js @@ -0,0 +1,127 @@ +goog.provide('ol.ImageBase'); +goog.provide('ol.ImageState'); + +goog.require('goog.events.EventTarget'); +goog.require('goog.events.EventType'); +goog.require('ol.Attribution'); +goog.require('ol.Extent'); + + +/** + * @enum {number} + */ +ol.ImageState = { + IDLE: 0, + LOADING: 1, + LOADED: 2, + ERROR: 3 +}; + + + +/** + * @constructor + * @extends {goog.events.EventTarget} + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.ImageState} state State. + * @param {Array.} attributions Attributions. + */ +ol.ImageBase = function(extent, resolution, pixelRatio, state, attributions) { + + goog.base(this); + + /** + * @private + * @type {Array.} + */ + this.attributions_ = attributions; + + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = extent; + + /** + * @private + * @type {number} + */ + this.pixelRatio_ = pixelRatio; + + /** + * @private + * @type {number} + */ + this.resolution_ = resolution; + + /** + * @protected + * @type {ol.ImageState} + */ + this.state = state; + +}; +goog.inherits(ol.ImageBase, goog.events.EventTarget); + + +/** + * @protected + */ +ol.ImageBase.prototype.dispatchChangeEvent = function() { + this.dispatchEvent(goog.events.EventType.CHANGE); +}; + + +/** + * @return {Array.} Attributions. + */ +ol.ImageBase.prototype.getAttributions = function() { + return this.attributions_; +}; + + +/** + * @return {ol.Extent} Extent. + */ +ol.ImageBase.prototype.getExtent = function() { + return this.extent_; +}; + + +/** + * @param {Object=} opt_context Object. + * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image. + */ +ol.ImageBase.prototype.getImageElement = goog.abstractMethod; + + +/** + * @return {number} PixelRatio. + */ +ol.ImageBase.prototype.getPixelRatio = function() { + return this.pixelRatio_; +}; + + +/** + * @return {number} Resolution. + */ +ol.ImageBase.prototype.getResolution = function() { + return this.resolution_; +}; + + +/** + * @return {ol.ImageState} State. + */ +ol.ImageBase.prototype.getState = function() { + return this.state; +}; + + +/** + * Load not yet loaded URI. + */ +ol.ImageBase.prototype.load = goog.abstractMethod; diff --git a/src/ol/imagecanvas.js b/src/ol/imagecanvas.js new file mode 100644 index 0000000000..3cd7ac9f2b --- /dev/null +++ b/src/ol/imagecanvas.js @@ -0,0 +1,38 @@ +goog.provide('ol.ImageCanvas'); + +goog.require('ol.ImageBase'); +goog.require('ol.ImageState'); + + + +/** + * @constructor + * @extends {ol.ImageBase} + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {Array.} attributions Attributions. + * @param {HTMLCanvasElement} canvas Canvas. + */ +ol.ImageCanvas = function(extent, resolution, pixelRatio, attributions, + canvas) { + + goog.base(this, extent, resolution, pixelRatio, ol.ImageState.LOADED, + attributions); + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = canvas; + +}; +goog.inherits(ol.ImageCanvas, ol.ImageBase); + + +/** + * @inheritDoc + */ +ol.ImageCanvas.prototype.getImageElement = function(opt_context) { + return this.canvas_; +}; diff --git a/src/ol/renderer/canvas/canvasimagelayerrenderer.js b/src/ol/renderer/canvas/canvasimagelayerrenderer.js index 3c653df019..5e004f6b96 100644 --- a/src/ol/renderer/canvas/canvasimagelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasimagelayerrenderer.js @@ -4,7 +4,7 @@ goog.require('goog.asserts'); goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('goog.vec.Mat4'); -goog.require('ol.Image'); +goog.require('ol.ImageBase'); goog.require('ol.ImageState'); goog.require('ol.ViewHint'); goog.require('ol.layer.Image'); @@ -27,7 +27,7 @@ ol.renderer.canvas.ImageLayer = function(mapRenderer, imageLayer) { /** * @private - * @type {?ol.Image} + * @type {?ol.ImageBase} */ this.image_ = null; diff --git a/src/ol/renderer/dom/domimagelayerrenderer.js b/src/ol/renderer/dom/domimagelayerrenderer.js index 3e82b6bac3..05e7a0d792 100644 --- a/src/ol/renderer/dom/domimagelayerrenderer.js +++ b/src/ol/renderer/dom/domimagelayerrenderer.js @@ -6,7 +6,7 @@ goog.require('goog.dom.TagName'); goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('goog.vec.Mat4'); -goog.require('ol.Image'); +goog.require('ol.ImageBase'); goog.require('ol.ImageState'); goog.require('ol.ViewHint'); goog.require('ol.dom'); @@ -32,7 +32,7 @@ ol.renderer.dom.ImageLayer = function(mapRenderer, imageLayer) { /** * The last rendered image. * @private - * @type {?ol.Image} + * @type {?ol.ImageBase} */ this.image_ = null; diff --git a/src/ol/renderer/webgl/webglimagelayerrenderer.js b/src/ol/renderer/webgl/webglimagelayerrenderer.js index 7a3485febc..4336ea8e37 100644 --- a/src/ol/renderer/webgl/webglimagelayerrenderer.js +++ b/src/ol/renderer/webgl/webglimagelayerrenderer.js @@ -7,7 +7,7 @@ goog.require('goog.vec.Mat4'); goog.require('goog.webgl'); goog.require('ol.Coordinate'); goog.require('ol.Extent'); -goog.require('ol.Image'); +goog.require('ol.ImageBase'); goog.require('ol.ImageState'); goog.require('ol.ViewHint'); goog.require('ol.layer.Image'); @@ -29,7 +29,7 @@ ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) { /** * The last rendered image. * @private - * @type {?ol.Image} + * @type {?ol.ImageBase} */ this.image_ = null; @@ -38,7 +38,7 @@ goog.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer); /** - * @param {ol.Image} image Image. + * @param {ol.ImageBase} image Image. * @private * @return {WebGLTexture} Texture. */ diff --git a/src/ol/source/imagecanvassource.exports b/src/ol/source/imagecanvassource.exports new file mode 100644 index 0000000000..280f37c7f2 --- /dev/null +++ b/src/ol/source/imagecanvassource.exports @@ -0,0 +1 @@ +@exportSymbol ol.source.ImageCanvas diff --git a/src/ol/source/imagecanvassource.js b/src/ol/source/imagecanvassource.js new file mode 100644 index 0000000000..478a4c00d6 --- /dev/null +++ b/src/ol/source/imagecanvassource.js @@ -0,0 +1,76 @@ +goog.provide('ol.source.ImageCanvas'); + +goog.require('ol.CanvasFunctionType'); +goog.require('ol.ImageCanvas'); +goog.require('ol.extent'); +goog.require('ol.source.Image'); + + + +/** + * @constructor + * @extends {ol.source.Image} + * @param {olx.source.ImageCanvasOptions} options + */ +ol.source.ImageCanvas = function(options) { + goog.base(this, { + attributions: options.attributions, + extent: options.extent, + logo: options.logo, + projection: options.projection, + resolutions: options.resolutions + }); + + /** + * @private + * @type {ol.CanvasFunctionType} + */ + this.canvasFunction_ = options.canvasFunction; + + /** + * @private + * @type {ol.ImageCanvas} + */ + this.canvas_ = null; + + /** + * @private + * @type {number} + */ + this.ratio_ = goog.isDef(options.ratio) ? + options.ratio : 1.5; + +}; +goog.inherits(ol.source.ImageCanvas, ol.source.Image); + + +/** + * @inheritDoc + */ +ol.source.ImageCanvas.prototype.getImage = + function(extent, resolution, pixelRatio, projection) { + resolution = this.findNearestResolution(resolution); + + var canvas = this.canvas_; + if (!goog.isNull(canvas) && + canvas.getResolution() == resolution && + canvas.getPixelRatio() == pixelRatio && + ol.extent.containsExtent(canvas.getExtent(), extent)) { + return canvas; + } + + extent = extent.slice(); + ol.extent.scaleFromCenter(extent, this.ratio_); + var width = (extent[2] - extent[0]) / resolution; + var height = (extent[3] - extent[1]) / resolution; + var size = [width * pixelRatio, height * pixelRatio]; + + var canvasElement = this.canvasFunction_(extent, resolution, size, + projection); + canvas = new ol.ImageCanvas(extent, resolution, pixelRatio, + this.getAttributions(), canvasElement); + this.canvas_ = canvas; + + return canvas; + +}; diff --git a/src/ol/source/imagesource.js b/src/ol/source/imagesource.js index 3bcf40e698..4374261597 100644 --- a/src/ol/source/imagesource.js +++ b/src/ol/source/imagesource.js @@ -5,23 +5,16 @@ goog.require('goog.asserts'); goog.require('ol.Attribution'); goog.require('ol.Extent'); goog.require('ol.Image'); -goog.require('ol.ImageUrlFunction'); -goog.require('ol.ImageUrlFunctionType'); -goog.require('ol.Size'); goog.require('ol.array'); goog.require('ol.source.Source'); /** * @typedef {{attributions: (Array.|undefined), - * crossOrigin: (null|string|undefined), * extent: (null|ol.Extent|undefined), * logo: (string|undefined), * projection: ol.proj.ProjectionLike, - * resolutions: (Array.|undefined), - * imageUrlFunction: (ol.ImageUrlFunctionType| - * undefined)}} - * @todo stability experimental + * resolutions: (Array.|undefined)}} */ ol.source.ImageOptions; @@ -42,22 +35,6 @@ ol.source.Image = function(options) { projection: options.projection }); - /** - * @protected - * @type {ol.ImageUrlFunctionType} - */ - this.imageUrlFunction = - goog.isDef(options.imageUrlFunction) ? - options.imageUrlFunction : - ol.ImageUrlFunction.nullImageUrlFunction; - - /** - * @protected - * @type {?string} - */ - this.crossOrigin = - goog.isDef(options.crossOrigin) ? options.crossOrigin : null; - /** * @private * @type {Array.} @@ -75,24 +52,10 @@ goog.inherits(ol.source.Image, ol.source.Source); /** - * @protected - * @param {ol.Extent} extent Extent. - * @param {number} resolution Resolution. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.Size} size Size. - * @param {ol.proj.Projection} projection Projection. - * @return {ol.Image} Single image. + * @return {Array.} Resolutions. */ -ol.source.Image.prototype.createImage = - function(extent, resolution, pixelRatio, size, projection) { - var image = null; - var imageUrl = this.imageUrlFunction(extent, size, projection); - if (goog.isDef(imageUrl)) { - image = new ol.Image( - extent, resolution, pixelRatio, imageUrl, this.crossOrigin, - this.getAttributions()); - } - return image; +ol.source.Image.prototype.getResolutions = function() { + return this.resolutions_; }; @@ -116,6 +79,6 @@ ol.source.Image.prototype.findNearestResolution = * @param {number} resolution Resolution. * @param {number} pixelRatio Pixel ratio. * @param {ol.proj.Projection} projection Projection. - * @return {ol.Image} Single image. + * @return {ol.ImageBase} Single image. */ ol.source.Image.prototype.getImage = goog.abstractMethod; diff --git a/src/ol/source/imagestaticsource.js b/src/ol/source/imagestaticsource.js index 38e534035c..52d9052570 100644 --- a/src/ol/source/imagestaticsource.js +++ b/src/ol/source/imagestaticsource.js @@ -1,7 +1,6 @@ goog.provide('ol.source.ImageStatic'); goog.require('ol.Image'); -goog.require('ol.ImageUrlFunctionType'); goog.require('ol.extent'); goog.require('ol.proj'); goog.require('ol.source.Image'); @@ -16,20 +15,21 @@ goog.require('ol.source.Image'); */ ol.source.ImageStatic = function(options) { - var imageFunction = ol.source.ImageStatic.createImageFunction( - options.url); - + var attributions = goog.isDef(options.attributions) ? + options.attributions : null; + var crossOrigin = goog.isDef(options.crossOrigin) ? + options.crossOrigin : null; var imageExtent = options.imageExtent; var imageSize = options.imageSize; var imageResolution = (imageExtent[3] - imageExtent[1]) / imageSize[1]; + var imageUrl = options.url; var projection = ol.proj.get(options.projection); goog.base(this, { - attributions: options.attributions, - crossOrigin: options.crossOrigin, + attributions: attributions, extent: options.extent, - projection: options.projection, - imageUrlFunction: imageFunction, + logo: options.logo, + projection: projection, resolutions: [imageResolution] }); @@ -37,8 +37,8 @@ ol.source.ImageStatic = function(options) { * @private * @type {ol.Image} */ - this.image_ = this.createImage( - imageExtent, imageResolution, 1, imageSize, projection); + this.image_ = new ol.Image(imageExtent, imageResolution, 1, attributions, + imageUrl, crossOrigin); }; goog.inherits(ol.source.ImageStatic, ol.source.Image); @@ -54,21 +54,3 @@ ol.source.ImageStatic.prototype.getImage = } return null; }; - - -/** - * @param {string|undefined} url URL. - * @return {ol.ImageUrlFunctionType} Function. - */ -ol.source.ImageStatic.createImageFunction = function(url) { - return ( - /** - * @param {ol.Extent} extent Extent. - * @param {ol.Size} size Size. - * @param {ol.proj.Projection} projection Projection. - * @return {string|undefined} URL. - */ - function(extent, size, projection) { - return url; - }); -}; diff --git a/src/ol/source/imagewmssource.js b/src/ol/source/imagewmssource.js index 481ec4eb87..8866368d48 100644 --- a/src/ol/source/imagewmssource.js +++ b/src/ol/source/imagewmssource.js @@ -24,12 +24,19 @@ ol.source.ImageWMS = function(opt_options) { goog.base(this, { attributions: options.attributions, - crossOrigin: options.crossOrigin, extent: options.extent, + logo: options.logo, projection: options.projection, resolutions: options.resolutions }); + /** + * @private + * @type {?string} + */ + this.crossOrigin_ = + goog.isDef(options.crossOrigin) ? options.crossOrigin : null; + /** * @private * @type {string|undefined} @@ -181,8 +188,8 @@ ol.source.ImageWMS.prototype.getImage = var url = goog.uri.utils.appendParamsFromMap(this.url_, params); - this.image_ = new ol.Image(extent, resolution, pixelRatio, url, - this.crossOrigin, this.getAttributions()); + this.image_ = new ol.Image(extent, resolution, pixelRatio, + this.getAttributions(), url, this.crossOrigin_); return this.image_; }; diff --git a/src/ol/source/mapguidesource.js b/src/ol/source/mapguidesource.js index cd831e629d..449e0adaa0 100644 --- a/src/ol/source/mapguidesource.js +++ b/src/ol/source/mapguidesource.js @@ -2,6 +2,7 @@ goog.provide('ol.source.MapGuide'); goog.require('goog.object'); goog.require('goog.uri.utils'); +goog.require('ol.Image'); goog.require('ol.ImageUrlFunction'); goog.require('ol.extent'); goog.require('ol.source.Image'); @@ -15,6 +16,26 @@ goog.require('ol.source.Image'); */ ol.source.MapGuide = function(options) { + goog.base(this, { + extent: options.extent, + projection: options.projection, + resolutions: options.resolutions + }); + + /** + * @private + * @type {?string} + */ + this.crossOrigin_ = + goog.isDef(options.crossOrigin) ? options.crossOrigin : null; + + /** + * @private + * @type {number} + */ + this.displayDpi_ = goog.isDef(options.displayDpi) ? + options.displayDpi : 96; + var imageUrlFunction; if (goog.isDef(options.url)) { var params = goog.isDef(options.params) ? options.params : {}; @@ -24,12 +45,11 @@ ol.source.MapGuide = function(options) { imageUrlFunction = ol.ImageUrlFunction.nullImageUrlFunction; } - goog.base(this, { - extent: options.extent, - projection: options.projection, - resolutions: options.resolutions, - imageUrlFunction: imageUrlFunction - }); + /** + * @private + * @type {ol.ImageUrlFunctionType} + */ + this.imageUrlFunction_ = imageUrlFunction; /** * @private @@ -37,13 +57,6 @@ ol.source.MapGuide = function(options) { */ this.hidpi_ = goog.isDef(options.hidpi) ? options.hidpi : true; - /** - * @private - * @type {number} - */ - this.displayDpi_ = goog.isDef(options.displayDpi) ? - options.displayDpi : 96; - /** * @private * @type {number} @@ -98,9 +111,16 @@ ol.source.MapGuide.prototype.getImage = var height = (extent[3] - extent[1]) / resolution; var size = [width * pixelRatio, height * pixelRatio]; - this.image_ = this.createImage( - extent, resolution, pixelRatio, size, projection); - return this.image_; + var imageUrl = this.imageUrlFunction_(extent, size, projection); + if (goog.isDef(imageUrl)) { + image = new ol.Image(extent, resolution, pixelRatio, + this.getAttributions(), imageUrl, this.crossOrigin_); + } else { + image = null; + } + this.image_ = image; + + return image; };