From 560931e9763cf990cae16083e6707b1ef8b35aec Mon Sep 17 00:00:00 2001 From: mike-000 <49240900+mike-000@users.noreply.github.com> Date: Sun, 15 Dec 2019 23:24:07 +0000 Subject: [PATCH] Set reprojection canvas context options Add example of disabling image smoothing Add test for reprojection context options --- examples/disable-image-smoothing.css | 15 +++ examples/disable-image-smoothing.html | 47 +++++++ examples/disable-image-smoothing.js | 115 ++++++++++++++++++ .../expected.png | Bin 0 -> 2404 bytes .../reproj-tile-disable-smoothing/main.js | 39 ++++++ src/ol/reproj.js | 6 +- src/ol/reproj/Tile.js | 14 ++- src/ol/source/BingMaps.js | 3 + src/ol/source/IIIF.js | 3 + src/ol/source/OSM.js | 5 +- src/ol/source/Stamen.js | 3 + src/ol/source/TileArcGISRest.js | 3 + src/ol/source/TileImage.js | 10 +- src/ol/source/TileJSON.js | 3 + src/ol/source/TileWMS.js | 3 + src/ol/source/WMTS.js | 3 + src/ol/source/XYZ.js | 3 + src/ol/source/Zoomify.js | 3 + 18 files changed, 272 insertions(+), 6 deletions(-) create mode 100644 examples/disable-image-smoothing.css create mode 100644 examples/disable-image-smoothing.html create mode 100644 examples/disable-image-smoothing.js create mode 100644 rendering/cases/reproj-tile-disable-smoothing/expected.png create mode 100644 rendering/cases/reproj-tile-disable-smoothing/main.js diff --git a/examples/disable-image-smoothing.css b/examples/disable-image-smoothing.css new file mode 100644 index 0000000000..db58102d58 --- /dev/null +++ b/examples/disable-image-smoothing.css @@ -0,0 +1,15 @@ +@media (min-width: 800px) { + .wrapper { + display: flex; + } + .half { + padding: 0 10px; + width: 50%; + float: left; + } +} +#opacity { + display: inline-block; + width: 150px; + vertical-align: text-bottom; +} diff --git a/examples/disable-image-smoothing.html b/examples/disable-image-smoothing.html new file mode 100644 index 0000000000..3d67e435fb --- /dev/null +++ b/examples/disable-image-smoothing.html @@ -0,0 +1,47 @@ +--- +layout: example.html +title: Disable Image Smoothing +shortdesc: Example of disabling image smoothing +docs: > + Example of disabling image smoothing when using raster DEM (digital elevation model) data. + The imageSmoothingEnabled (or for Internet Explorer msImageSmoothingEnabled) canvas + context property is set to false at the layer's prerender event. Additionally for a + reprojected source those properties must also be also be specified for the canvas contexts used in + the reprojection via the source's reprojectionContextOptions option. Elevation data is + calculated from the pixel value returned by forEachLayerAtPixel. For comparison a second map + with smoothing enabled returns inaccuate elevations which are very noticeable close to 3107 meters + due to how elevation is calculated from the pixel value. +tags: "disable image smoothing, xyz, maptiler, reprojection" +cloak: + - key: get_your_own_D6rA4zTHduk6KOKTXzGB + value: Get your own API key at https://www.maptiler.com/cloud/ +--- + + + Smoothing Disabled + + + + Elevation + 0.0 meters + + + + + Imagery opacity + + % + + + + + Uncorrected Comparison + + + + Elevation + 0.0 meters + + + + diff --git a/examples/disable-image-smoothing.js b/examples/disable-image-smoothing.js new file mode 100644 index 0000000000..52ab34265b --- /dev/null +++ b/examples/disable-image-smoothing.js @@ -0,0 +1,115 @@ +import Map from '../src/ol/Map.js'; +import View from '../src/ol/View.js'; +import TileLayer from '../src/ol/layer/Tile.js'; +import XYZ from '../src/ol/source/XYZ.js'; + +const key = 'get_your_own_D6rA4zTHduk6KOKTXzGB'; +const attributions = '© MapTiler ' + + '© OpenStreetMap contributors'; + +const disabledLayer = new TileLayer({ + // specify className so forEachLayerAtPixel can distinguish layers + className: 'ol-layer-dem', + source: new XYZ({ + attributions: attributions, + url: 'https://api.maptiler.com/tiles/terrain-rgb/{z}/{x}/{y}.png?key=' + key, + maxZoom: 10, + crossOrigin: '', + reprojectionContextOptions: {imageSmoothingEnabled: false, msImageSmoothingEnabled: false} + }) +}); + +const imagery = new TileLayer({ + className: 'ol-layer-imagery', + source: new XYZ({ + attributions: attributions, + url: 'https://api.maptiler.com/tiles/satellite/{z}/{x}/{y}.jpg?key=' + key, + maxZoom: 20, + crossOrigin: '' + }) +}); + +const enabledLayer = new TileLayer({ + source: new XYZ({ + attributions: attributions, + url: 'https://api.maptiler.com/tiles/terrain-rgb/{z}/{x}/{y}.png?key=' + key, + maxZoom: 10, + crossOrigin: '' + }) +}); + +disabledLayer.on('prerender', function(evt) { + evt.context.imageSmoothingEnabled = false; + evt.context.msImageSmoothingEnabled = false; +}); + +imagery.on('prerender', function(evt) { + // use opaque background to conceal DEM while fully opaque imagery renders + if (imagery.getOpacity() === 1) { + evt.context.fillStyle = 'white'; + evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height); + } +}); + +const control = document.getElementById('opacity'); +const output = document.getElementById('output'); +control.addEventListener('input', function() { + output.innerText = control.value; + imagery.setOpacity(control.value / 100); +}); +output.innerText = control.value; +imagery.setOpacity(control.value / 100); + +const view = new View({ + center: [6.893, 45.8295], + zoom: 16, + projection: 'EPSG:4326' +}); + +const map1 = new Map({ + target: 'map1', + layers: [disabledLayer, imagery], + view: view +}); + +const map2 = new Map({ + target: 'map2', + layers: [enabledLayer], + view: view +}); + +const info1 = document.getElementById('info1'); +const info2 = document.getElementById('info2'); + +const showElevations = function(evt) { + if (evt.dragging) { + return; + } + map1.forEachLayerAtPixel( + evt.pixel, + function(layer, pixel) { + const height = -10000 + (pixel[0] * 256 * 256 + pixel[1] * 256 + pixel[2]) * 0.1; + info1.innerText = height.toFixed(1); + }, + { + layerFilter: function(layer) { + return layer === disabledLayer; + } + } + ); + map2.forEachLayerAtPixel( + evt.pixel, + function(layer, pixel) { + const height = -10000 + (pixel[0] * 256 * 256 + pixel[1] * 256 + pixel[2]) * 0.1; + info2.innerText = height.toFixed(1); + }, + { + layerFilter: function(layer) { + return layer === enabledLayer; + } + } + ); +}; + +map1.on('pointermove', showElevations); +map2.on('pointermove', showElevations); diff --git a/rendering/cases/reproj-tile-disable-smoothing/expected.png b/rendering/cases/reproj-tile-disable-smoothing/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..6c51884467687ebd9265be2ea10c1571fb0504ad GIT binary patch literal 2404 zcmdT_ZAepL6h7CvO=nI)L21s3e`ZB4BGB^d7M2ccN(6?OTSTEzIYOL?ZC5jAAs8lU zXgWe%Kg4NTh?#d}VkNbuV5Im-Ii}8S4rfm9y}kR<|9%9`Kksv1-t&Gu?>Xl=4{~G~ z!My!E00@?5rk@AEkcmM62SNVr7uNs+4@uKcUMMTD&GuezQbmm}CS26URxPo^OOzjC z!>joPZ(C$SVKwhyZ)0_19$nOOX6T_w**0f%rW_}=#e+ZAJR3{vk+glpdd0Ju%M|Fh zCDCpA?RPVrF91Yo)xAzXY2anZHLz$f=5&<5#*F)&S2yoFq%q!Kb$w;0F=8(?O)-A{>i1o`86WRQ_< z=7+*fCLSX2K^!_Z-}thVrj;h847=E%Ip>n6sGlwyw5*r{#5zY73Ric=7XJ`SgI(TU zBKB7iY1flfA2p6=YjzLV7CurLMQS0R@Q6b#EC7|0GpY?nUjYFkh0ts0)b}r^A|xEx z)K~AqN^ayb^7MTNMx_#TipF+GO_r-Yt?bG2w?+DB4i#4GLW&WA?*@#QdHMVds_XMg z$ou-9IxfPTVPm2Ip|3kQ*HrR(eekc5n5GEG6c3;vx-E7I>lJ0vvvkh@Rjp+bGs^(g zrOOJ1Vjx~K+a=H+2KWq9aRR0TL1)vX3c=w|Zq%L`Ka<} [resolutions] Supported resolutions as given in IIIF 'scaleFactors' * @property {import("../size.js").Size} size Size of the image [width, height]. * @property {Array} [sizes] Supported scaled image sizes. @@ -274,6 +276,7 @@ class IIIF extends TileImage { crossOrigin: options.crossOrigin, projection: options.projection, reprojectionErrorThreshold: options.reprojectionErrorThreshold, + reprojectionContextOptions: options.reprojectionContextOptions, state: options.state, tileClass: IiifTileClass, tileGrid: tileGrid, diff --git a/src/ol/source/OSM.js b/src/ol/source/OSM.js index f95f4589a6..d2f52a6d07 100644 --- a/src/ol/source/OSM.js +++ b/src/ol/source/OSM.js @@ -26,8 +26,10 @@ export const ATTRIBUTION = '© ' + * See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for more detail. * @property {number} [maxZoom=19] Max zoom. * @property {boolean} [opaque=true] Whether the layer is opaque. - * @property {number} [reprojectionErrorThreshold=1.5] Maximum allowed reprojection error (in pixels). + * @property {number} [reprojectionErrorThreshold=0.5] Maximum allowed reprojection error (in pixels). * Higher values can increase reprojection performance, but decrease precision. + * @property {object} [reprojectionContextOptions] Optional properties to set on the canvas context used + * for reprojection. For example specify `{imageSmoothingEnabled: false}` to disable image smoothing. * @property {import("../Tile.js").LoadFunction} [tileLoadFunction] Optional function to load a tile given a URL. The default is * ```js * function(imageTile, src) { @@ -73,6 +75,7 @@ class OSM extends XYZ { opaque: options.opaque !== undefined ? options.opaque : true, maxZoom: options.maxZoom !== undefined ? options.maxZoom : 19, reprojectionErrorThreshold: options.reprojectionErrorThreshold, + reprojectionContextOptions: options.reprojectionContextOptions, tileLoadFunction: options.tileLoadFunction, url: url, wrapX: options.wrapX, diff --git a/src/ol/source/Stamen.js b/src/ol/source/Stamen.js index d3f768a426..cc8c2ce594 100644 --- a/src/ol/source/Stamen.js +++ b/src/ol/source/Stamen.js @@ -96,6 +96,8 @@ const ProviderConfig = { * @property {number} [maxZoom] Maximum zoom. * @property {number} [reprojectionErrorThreshold=0.5] Maximum allowed reprojection error (in pixels). * Higher values can increase reprojection performance, but decrease precision. + * @property {object} [reprojectionContextOptions] Optional properties to set on the canvas context used + * for reprojection. For example specify `{imageSmoothingEnabled: false}` to disable image smoothing. * @property {import("../Tile.js").LoadFunction} [tileLoadFunction] * Optional function to load a tile given a URL. The default is * ```js @@ -138,6 +140,7 @@ class Stamen extends XYZ { minZoom: options.minZoom != undefined ? options.minZoom : providerConfig.minZoom, opaque: layerConfig.opaque, reprojectionErrorThreshold: options.reprojectionErrorThreshold, + reprojectionContextOptions: options.reprojectionContextOptions, tileLoadFunction: options.tileLoadFunction, transition: options.transition, url: url, diff --git a/src/ol/source/TileArcGISRest.js b/src/ol/source/TileArcGISRest.js index 5e81b927d6..8576b7d2d0 100644 --- a/src/ol/source/TileArcGISRest.js +++ b/src/ol/source/TileArcGISRest.js @@ -34,6 +34,8 @@ import {appendParams} from '../uri.js'; * @property {import("../proj.js").ProjectionLike} [projection] Projection. Default is the view projection. * @property {number} [reprojectionErrorThreshold=0.5] Maximum allowed reprojection error (in pixels). * Higher values can increase reprojection performance, but decrease precision. + * @property {object} [reprojectionContextOptions] Optional properties to set on the canvas context used + * for reprojection. For example specify `{imageSmoothingEnabled: false}` to disable image smoothing. * @property {import("../Tile.js").LoadFunction} [tileLoadFunction] Optional function to load a tile given a URL. * The default is * ```js @@ -74,6 +76,7 @@ class TileArcGISRest extends TileImage { crossOrigin: options.crossOrigin, projection: options.projection, reprojectionErrorThreshold: options.reprojectionErrorThreshold, + reprojectionContextOptions: options.reprojectionContextOptions, tileGrid: options.tileGrid, tileLoadFunction: options.tileLoadFunction, tileUrlFunction: tileUrlFunction, diff --git a/src/ol/source/TileImage.js b/src/ol/source/TileImage.js index ac4ccf709e..887a23fc3b 100644 --- a/src/ol/source/TileImage.js +++ b/src/ol/source/TileImage.js @@ -25,6 +25,8 @@ import {getForProjection as getTileGridForProjection} from '../tilegrid.js'; * @property {import("../proj.js").ProjectionLike} [projection] Projection. Default is the view projection. * @property {number} [reprojectionErrorThreshold=0.5] Maximum allowed reprojection error (in pixels). * Higher values can increase reprojection performance, but decrease precision. + * @property {object} [reprojectionContextOptions] Optional properties to set on the canvas context used + * for reprojection. For example specify `{imageSmoothingEnabled: false}` to disable image smoothing. * @property {import("./State.js").default} [state] Source state. * @property {typeof import("../ImageTile.js").default} [tileClass] Class used to instantiate image tiles. * Default is {@link module:ol/ImageTile~ImageTile}. @@ -123,6 +125,12 @@ class TileImage extends UrlTile { */ this.reprojectionErrorThreshold_ = options.reprojectionErrorThreshold; + /** + * @private + * @type {object|undefined} + */ + this.reprojectionContextOptions_ = options.reprojectionContextOptions; + /** * @private * @type {boolean} @@ -296,7 +304,7 @@ class TileImage extends UrlTile { function(z, x, y, pixelRatio) { return this.getTileInternal(z, x, y, pixelRatio, sourceProjection); }.bind(this), this.reprojectionErrorThreshold_, - this.renderReprojectionEdges_); + this.renderReprojectionEdges_, this.reprojectionContextOptions_); newTile.key = key; if (tile) { diff --git a/src/ol/source/TileJSON.js b/src/ol/source/TileJSON.js index 6fe4877095..5233766869 100644 --- a/src/ol/source/TileJSON.js +++ b/src/ol/source/TileJSON.js @@ -47,6 +47,8 @@ import {createXYZ, extentFromProjection} from '../tilegrid.js'; * Useful when the server does not support CORS.. * @property {number} [reprojectionErrorThreshold=0.5] Maximum allowed reprojection error (in pixels). * Higher values can increase reprojection performance, but decrease precision. + * @property {object} [reprojectionContextOptions] Optional properties to set on the canvas context used + * for reprojection. For example specify `{imageSmoothingEnabled: false}` to disable image smoothing. * @property {Config} [tileJSON] TileJSON configuration for this source. * If not provided, `url` must be configured. * @property {import("../Tile.js").LoadFunction} [tileLoadFunction] Optional function to load a tile given a URL. The default is @@ -80,6 +82,7 @@ class TileJSON extends TileImage { crossOrigin: options.crossOrigin, projection: getProjection('EPSG:3857'), reprojectionErrorThreshold: options.reprojectionErrorThreshold, + reprojectionContextOptions: options.reprojectionContextOptions, state: SourceState.LOADING, tileLoadFunction: options.tileLoadFunction, wrapX: options.wrapX !== undefined ? options.wrapX : true, diff --git a/src/ol/source/TileWMS.js b/src/ol/source/TileWMS.js index 513e7ae132..f7b1ec5467 100644 --- a/src/ol/source/TileWMS.js +++ b/src/ol/source/TileWMS.js @@ -42,6 +42,8 @@ import {appendParams} from '../uri.js'; * @property {import("../proj.js").ProjectionLike} [projection] Projection. Default is the view projection. * @property {number} [reprojectionErrorThreshold=0.5] Maximum allowed reprojection error (in pixels). * Higher values can increase reprojection performance, but decrease precision. + * @property {object} [reprojectionContextOptions] Optional properties to set on the canvas context used + * for reprojection. For example specify `{imageSmoothingEnabled: false}` to disable image smoothing. * @property {typeof import("../ImageTile.js").default} [tileClass] Class used to instantiate image tiles. * Default is {@link module:ol/ImageTile~ImageTile}. * @property {import("../tilegrid/TileGrid.js").default} [tileGrid] Tile grid. Base this on the resolutions, @@ -94,6 +96,7 @@ class TileWMS extends TileImage { opaque: !transparent, projection: options.projection, reprojectionErrorThreshold: options.reprojectionErrorThreshold, + reprojectionContextOptions: options.reprojectionContextOptions, tileClass: options.tileClass, tileGrid: options.tileGrid, tileLoadFunction: options.tileLoadFunction, diff --git a/src/ol/source/WMTS.js b/src/ol/source/WMTS.js index 603bc78e80..9de0f9a2e2 100644 --- a/src/ol/source/WMTS.js +++ b/src/ol/source/WMTS.js @@ -23,6 +23,8 @@ import {appendParams} from '../uri.js'; * @property {import("../proj.js").ProjectionLike} [projection] Projection. Default is the view projection. * @property {number} [reprojectionErrorThreshold=0.5] Maximum allowed reprojection error (in pixels). * Higher values can increase reprojection performance, but decrease precision. + * @property {object} [reprojectionContextOptions] Optional properties to set on the canvas context used + * for reprojection. For example specify `{imageSmoothingEnabled: false}` to disable image smoothing. * @property {import("./WMTSRequestEncoding.js").default|string} [requestEncoding='KVP'] Request encoding. * @property {string} layer Layer name as advertised in the WMTS capabilities. * @property {string} style Style name as advertised in the WMTS capabilities. @@ -87,6 +89,7 @@ class WMTS extends TileImage { crossOrigin: options.crossOrigin, projection: options.projection, reprojectionErrorThreshold: options.reprojectionErrorThreshold, + reprojectionContextOptions: options.reprojectionContextOptions, tileClass: options.tileClass, tileGrid: tileGrid, tileLoadFunction: options.tileLoadFunction, diff --git a/src/ol/source/XYZ.js b/src/ol/source/XYZ.js index 6c550057a5..1195286c37 100644 --- a/src/ol/source/XYZ.js +++ b/src/ol/source/XYZ.js @@ -17,6 +17,8 @@ import {createXYZ, extentFromProjection} from '../tilegrid.js'; * @property {import("../proj.js").ProjectionLike} [projection='EPSG:3857'] Projection. * @property {number} [reprojectionErrorThreshold=0.5] Maximum allowed reprojection error (in pixels). * Higher values can increase reprojection performance, but decrease precision. + * @property {object} [reprojectionContextOptions] Optional properties to set on the canvas context used + * for reprojection. For example specify `{imageSmoothingEnabled: false}` to disable image smoothing. * @property {number} [maxZoom=18] Optional max zoom level. * @property {number} [minZoom=0] Optional min zoom level. * @property {import("../tilegrid/TileGrid.js").default} [tileGrid] Tile grid. @@ -90,6 +92,7 @@ class XYZ extends TileImage { opaque: options.opaque, projection: projection, reprojectionErrorThreshold: options.reprojectionErrorThreshold, + reprojectionContextOptions: options.reprojectionContextOptions, tileGrid: tileGrid, tileLoadFunction: options.tileLoadFunction, tilePixelRatio: options.tilePixelRatio, diff --git a/src/ol/source/Zoomify.js b/src/ol/source/Zoomify.js index 5a5bd2efa1..401c788129 100644 --- a/src/ol/source/Zoomify.js +++ b/src/ol/source/Zoomify.js @@ -93,6 +93,8 @@ export class CustomTile extends ImageTile { * @property {number} [tilePixelRatio] The pixel ratio used by the tile service. For example, if the tile service advertizes 256px by 256px tiles but actually sends 512px by 512px images (for retina/hidpi devices) then `tilePixelRatio` should be set to `2` * @property {number} [reprojectionErrorThreshold=0.5] Maximum allowed reprojection error (in pixels). * Higher values can increase reprojection performance, but decrease precision. + * @property {object} [reprojectionContextOptions] Optional properties to set on the canvas context used + * for reprojection. For example specify `{imageSmoothingEnabled: false}` to disable image smoothing. * @property {string} [url] URL template or base URL of the Zoomify service. * A base URL is the fixed part * of the URL, excluding the tile group, z, x, and y folder structure, e.g. @@ -255,6 +257,7 @@ class Zoomify extends TileImage { projection: options.projection, tilePixelRatio: tilePixelRatio, reprojectionErrorThreshold: options.reprojectionErrorThreshold, + reprojectionContextOptions: options.reprojectionContextOptions, tileClass: ZoomifyTileClass, tileGrid: tileGrid, tileUrlFunction: tileUrlFunction,