diff --git a/examples/reprojection-image.html b/examples/reprojection-image.html index 6cd557afa4..5a3aa14249 100644 --- a/examples/reprojection-image.html +++ b/examples/reprojection-image.html @@ -7,3 +7,4 @@ docs: > tags: "reprojection, projection, proj4js, image, imagestatic" ---
+
Image smoothing
diff --git a/examples/reprojection-image.js b/examples/reprojection-image.js index 2d6c815b03..24879205cd 100644 --- a/examples/reprojection-image.js +++ b/examples/reprojection-image.js @@ -18,22 +18,14 @@ proj4.defs( register(proj4); const imageExtent = [0, 0, 700000, 1300000]; +const imageLayer = new ImageLayer(); const map = new Map({ layers: [ new TileLayer({ source: new OSM(), }), - new ImageLayer({ - source: new Static({ - url: - 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/18/' + - 'British_National_Grid.svg/2000px-British_National_Grid.svg.png', - crossOrigin: '', - projection: 'EPSG:27700', - imageExtent: imageExtent, - }), - }), + imageLayer, ], target: 'map', view: new View({ @@ -41,3 +33,21 @@ const map = new Map({ zoom: 4, }), }); + +const imageSmoothing = document.getElementById('imageSmoothing'); + +function setSource() { + const source = new Static({ + url: + 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/18/' + + 'British_National_Grid.svg/2000px-British_National_Grid.svg.png', + crossOrigin: '', + projection: 'EPSG:27700', + imageExtent: imageExtent, + imageSmoothing: imageSmoothing.checked, + }); + imageLayer.setSource(source); +} +setSource(); + +imageSmoothing.addEventListener('change', setSource); diff --git a/rendering/cases/image-disable-smoothing/expected.png b/rendering/cases/image-disable-smoothing/expected.png new file mode 100644 index 0000000000..8735dd9d86 Binary files /dev/null and b/rendering/cases/image-disable-smoothing/expected.png differ diff --git a/rendering/cases/image-disable-smoothing/main.js b/rendering/cases/image-disable-smoothing/main.js new file mode 100644 index 0000000000..aef17f42d2 --- /dev/null +++ b/rendering/cases/image-disable-smoothing/main.js @@ -0,0 +1,29 @@ +import ImageLayer from '../../../src/ol/layer/Image.js'; +import Map from '../../../src/ol/Map.js'; +import Static from '../../../src/ol/source/ImageStatic.js'; +import View from '../../../src/ol/View.js'; +import {fromLonLat, transformExtent} from '../../../src/ol/proj.js'; + +const source = new Static({ + url: '/data/tiles/osm/5/5/12.png', + imageExtent: transformExtent([-123, 37, -122, 38], 'EPSG:4326', 'EPSG:3857'), + imageSmoothing: false, +}); + +new Map({ + pixelRatio: 1, + target: 'map', + layers: [ + new ImageLayer({ + source: source, + }), + ], + view: new View({ + center: fromLonLat([-122.416667, 37.783333]), + zoom: 12, + }), +}); + +render({ + tolerance: 0.001, +}); diff --git a/rendering/cases/reproj-image-disable-smoothing/expected.png b/rendering/cases/reproj-image-disable-smoothing/expected.png new file mode 100644 index 0000000000..d2050f0742 Binary files /dev/null and b/rendering/cases/reproj-image-disable-smoothing/expected.png differ diff --git a/rendering/cases/reproj-image-disable-smoothing/main.js b/rendering/cases/reproj-image-disable-smoothing/main.js new file mode 100644 index 0000000000..5383d316ca --- /dev/null +++ b/rendering/cases/reproj-image-disable-smoothing/main.js @@ -0,0 +1,31 @@ +import ImageLayer from '../../../src/ol/layer/Image.js'; +import Map from '../../../src/ol/Map.js'; +import Static from '../../../src/ol/source/ImageStatic.js'; +import View from '../../../src/ol/View.js'; +import {get as getProjection, transformExtent} from '../../../src/ol/proj.js'; + +const source = new Static({ + url: '/data/tiles/osm/5/5/12.png', + imageExtent: transformExtent([-123, 37, -122, 38], 'EPSG:4326', 'EPSG:3857'), + imageSmoothing: false, + projection: getProjection('EPSG:3857'), +}); + +new Map({ + pixelRatio: 1, + target: 'map', + layers: [ + new ImageLayer({ + source: source, + }), + ], + view: new View({ + center: [-122.416667, 37.783333], + zoom: 12, + projection: 'EPSG:4326', + }), +}); + +render({ + tolerance: 0.001, +}); diff --git a/src/ol/renderer/canvas/ImageLayer.js b/src/ol/renderer/canvas/ImageLayer.js index 7cf79ca989..72e6933b90 100644 --- a/src/ol/renderer/canvas/ImageLayer.js +++ b/src/ol/renderer/canvas/ImageLayer.js @@ -4,6 +4,7 @@ import CanvasLayerRenderer from './Layer.js'; import ViewHint from '../../ViewHint.js'; import {ENABLE_RASTER_REPROJECTION} from '../../reproj/common.js'; +import {assign} from '../../obj.js'; import {compose as composeTransform, makeInverse} from '../../transform.js'; import {containsExtent, intersects} from '../../extent.js'; import {createTransformString} from '../../render/canvas.js'; @@ -180,6 +181,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { const dw = img.width * transform[0]; const dh = img.height * transform[3]; + assign(context, this.getLayer().getSource().getContextOptions()); this.preRender(context, frameState); if (dw >= 0.5 && dh >= 0.5) { const opacity = layerState.opacity; diff --git a/src/ol/reproj/Image.js b/src/ol/reproj/Image.js index af44175bc2..bbb6e8b1e6 100644 --- a/src/ol/reproj/Image.js +++ b/src/ol/reproj/Image.js @@ -32,6 +32,7 @@ class ReprojImage extends ImageBase { * @param {number} pixelRatio Pixel ratio. * @param {FunctionType} getImageFunction * Function returning source images (extent, resolution, pixelRatio). + * @param {object=} opt_contextOptions Properties to set on the canvas context. */ constructor( sourceProj, @@ -39,7 +40,8 @@ class ReprojImage extends ImageBase { targetExtent, targetResolution, pixelRatio, - getImageFunction + getImageFunction, + opt_contextOptions ) { const maxSourceExtent = sourceProj.getExtent(); const maxTargetExtent = targetProj.getExtent(); @@ -120,6 +122,12 @@ class ReprojImage extends ImageBase { */ this.sourcePixelRatio_ = sourcePixelRatio; + /** + * @private + * @type {object} + */ + this.contextOptions_ = opt_contextOptions; + /** * @private * @type {HTMLCanvasElement} @@ -181,7 +189,9 @@ class ReprojImage extends ImageBase { image: this.sourceImage_.getImage(), }, ], - 0 + 0, + undefined, + this.contextOptions_ ); } this.state = sourceState; diff --git a/src/ol/source/Image.js b/src/ol/source/Image.js index dd50c89271..2c62214b1b 100644 --- a/src/ol/source/Image.js +++ b/src/ol/source/Image.js @@ -6,6 +6,7 @@ import ImageState from '../ImageState.js'; import ReprojImage from '../reproj/Image.js'; import Source from './Source.js'; import {ENABLE_RASTER_REPROJECTION} from '../reproj/common.js'; +import {IMAGE_SMOOTHING_DISABLED} from './common.js'; import {abstract} from '../util.js'; import {equals} from '../extent.js'; import {equivalent} from '../proj.js'; @@ -62,6 +63,7 @@ export class ImageSourceEvent extends Event { /** * @typedef {Object} Options * @property {import("./Source.js").AttributionLike} [attributions] + * @property {boolean} [imageSmoothing=true] Enable image smoothing. * @property {import("../proj.js").ProjectionLike} [projection] * @property {Array} [resolutions] * @property {import("./State.js").default} [state] @@ -105,6 +107,13 @@ class ImageSource extends Source { * @type {number} */ this.reprojectedRevision_ = 0; + + /** + * @private + * @type {object|undefined} + */ + this.contextOptions_ = + options.imageSmoothing === false ? IMAGE_SMOOTHING_DISABLED : undefined; } /** @@ -114,6 +123,13 @@ class ImageSource extends Source { return this.resolutions_; } + /** + * @return {Object|undefined} Context options. + */ + getContextOptions() { + return this.contextOptions_; + } + /** * @protected * @param {number} resolution Resolution. @@ -173,7 +189,8 @@ class ImageSource extends Source { pixelRatio, sourceProjection ); - }.bind(this) + }.bind(this), + this.contextOptions_ ); this.reprojectedRevision_ = this.getRevision(); diff --git a/src/ol/source/ImageArcGISRest.js b/src/ol/source/ImageArcGISRest.js index b4c74c2ba2..21c60cabb7 100644 --- a/src/ol/source/ImageArcGISRest.js +++ b/src/ol/source/ImageArcGISRest.js @@ -20,6 +20,7 @@ import {containsExtent, getHeight, getWidth} from '../extent.js'; * the remote server. * @property {import("../Image.js").LoadFunction} [imageLoadFunction] Optional function to load an image given * a URL. + * @property {boolean} [imageSmoothing=true] Enable image smoothing. * @property {Object} [params] ArcGIS Rest parameters. This field is optional. Service * defaults will be used for any fields not specified. `FORMAT` is `PNG32` by default. `F` is * `IMAGE` by default. `TRANSPARENT` is `true` by default. `BBOX`, `SIZE`, `BBOXSR`, and `IMAGESR` @@ -56,6 +57,7 @@ class ImageArcGISRest extends ImageSource { super({ attributions: options.attributions, + imageSmoothing: options.imageSmoothing, projection: options.projection, resolutions: options.resolutions, }); diff --git a/src/ol/source/ImageCanvas.js b/src/ol/source/ImageCanvas.js index 576b77060e..bc73fe22b2 100644 --- a/src/ol/source/ImageCanvas.js +++ b/src/ol/source/ImageCanvas.js @@ -36,6 +36,7 @@ import { * the value returned by the function is later changed then * `changed` should be called on the source for the source to * invalidate the current cached image. See: {@link module:ol/Observable~Observable#changed} + * @property {boolean} [imageSmoothing=true] Enable image smoothing. * @property {import("../proj.js").ProjectionLike} [projection] Projection. Default is the view projection. * @property {number} [ratio=1.5] Ratio. 1 means canvases are the size of the map viewport, 2 means twice the * width and height of the map viewport, and so on. Must be `1` or higher. @@ -58,6 +59,7 @@ class ImageCanvasSource extends ImageSource { super({ attributions: options.attributions, + imageSmoothing: options.imageSmoothing, projection: options.projection, resolutions: options.resolutions, state: options.state, diff --git a/src/ol/source/ImageMapGuide.js b/src/ol/source/ImageMapGuide.js index 98afc1eb4c..e4a0ff56af 100644 --- a/src/ol/source/ImageMapGuide.js +++ b/src/ol/source/ImageMapGuide.js @@ -32,6 +32,7 @@ import { * @property {Array} [resolutions] Resolutions. * If specified, requests will be made for these resolutions only. * @property {import("../Image.js").LoadFunction} [imageLoadFunction] Optional function to load an image given a URL. + * @property {boolean} [imageSmoothing=true] Enable image smoothing. * @property {Object} [params] Additional parameters. */ @@ -48,6 +49,7 @@ class ImageMapGuide extends ImageSource { */ constructor(options) { super({ + imageSmoothing: options.imageSmoothing, projection: options.projection, resolutions: options.resolutions, }); diff --git a/src/ol/source/ImageStatic.js b/src/ol/source/ImageStatic.js index c8aa5a1924..fc9276b618 100644 --- a/src/ol/source/ImageStatic.js +++ b/src/ol/source/ImageStatic.js @@ -19,6 +19,7 @@ import {get as getProjection} from '../proj.js'; * @property {import("../extent.js").Extent} [imageExtent] Extent of the image in map coordinates. * This is the [left, bottom, right, top] map coordinates of your image. * @property {import("../Image.js").LoadFunction} [imageLoadFunction] Optional function to load an image given a URL. + * @property {boolean} [imageSmoothing=true] Enable image smoothing. * @property {import("../proj.js").ProjectionLike} [projection] Projection. Default is the view projection. * @property {import("../size.js").Size} [imageSize] Size of the image in pixels. Usually the image size is auto-detected, so this * only needs to be set if auto-detection fails for some reason. @@ -45,6 +46,7 @@ class Static extends ImageSource { super({ attributions: options.attributions, + imageSmoothing: options.imageSmoothing, projection: getProjection(options.projection), }); diff --git a/src/ol/source/ImageWMS.js b/src/ol/source/ImageWMS.js index a74d02ec81..93cadb954b 100644 --- a/src/ol/source/ImageWMS.js +++ b/src/ol/source/ImageWMS.js @@ -39,6 +39,7 @@ const GETFEATUREINFO_IMAGE_SIZE = [101, 101]; * @property {import("./WMSServerType.js").default|string} [serverType] The type of * the remote WMS server: `mapserver`, `geoserver` or `qgis`. Only needed if `hidpi` is `true`. * @property {import("../Image.js").LoadFunction} [imageLoadFunction] Optional function to load an image given a URL. + * @property {boolean} [imageSmoothing=true] Enable image smoothing. * @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` @@ -68,6 +69,7 @@ class ImageWMS extends ImageSource { super({ attributions: options.attributions, + imageSmoothing: options.imageSmoothing, projection: options.projection, resolutions: options.resolutions, }); diff --git a/src/ol/source/Source.js b/src/ol/source/Source.js index 5655054a2b..af2898cfec 100644 --- a/src/ol/source/Source.js +++ b/src/ol/source/Source.js @@ -140,6 +140,13 @@ class Source extends BaseObject { return this.wrapX_; } + /** + * @return {Object|undefined} Context options. + */ + getContextOptions() { + return undefined; + } + /** * Refreshes the source. The source will be cleared, and data from the server will be reloaded. * @api diff --git a/src/ol/source/Tile.js b/src/ol/source/Tile.js index f2eb329882..901386fa8a 100644 --- a/src/ol/source/Tile.js +++ b/src/ol/source/Tile.js @@ -173,13 +173,6 @@ class TileSource extends Source { return covered; } - /** - * @return {Object|undefined} Context options. - */ - getContextOptions() { - return undefined; - } - /** * @param {import("../proj/Projection.js").default} projection Projection. * @return {number} Gutter.