diff --git a/src/canvas/quadFeature.js b/src/canvas/quadFeature.js index d7e600d573..dad410440a 100644 --- a/src/canvas/quadFeature.js +++ b/src/canvas/quadFeature.js @@ -138,6 +138,14 @@ var canvas_quadFeature = function (arg) { var oldAlpha = context2d.globalAlpha; var opacity = oldAlpha; + var nearestPixel = m_this.nearestPixel(); + if (nearestPixel !== undefined) { + if (nearestPixel !== true && util.isNonNullFinite(nearestPixel)) { + const curZoom = m_this.layer().map().zoom(); + nearestPixel = curZoom >= nearestPixel; + } + context2d.imageSmoothingEnabled = !nearestPixel; + } $.each([m_quads.imgQuads, m_quads.vidQuads], function (listidx, quadlist) { if (!quadlist) { return; @@ -193,6 +201,7 @@ var canvas_quadFeature = function (arg) { context2d.globalAlpha = oldAlpha; } context2d.setTransform(1, 0, 0, 1, 0, 0); + context2d.imageSmoothingEnabled = true; }; /** diff --git a/src/canvas/tileLayer.js b/src/canvas/tileLayer.js index 92af1b027c..634d41ad79 100644 --- a/src/canvas/tileLayer.js +++ b/src/canvas/tileLayer.js @@ -69,6 +69,7 @@ var canvas_tileLayer = function () { */ this._update = function (request) { s_update.call(m_this, request); + m_quadFeature.nearestPixel(m_this.nearestPixel()); m_this._addBaseQuadToTiles(m_quadFeature, m_tiles); return m_this; }; diff --git a/src/map.js b/src/map.js index 7435936e71..88a22dedf0 100644 --- a/src/map.js +++ b/src/map.js @@ -1964,6 +1964,9 @@ var map = function (arg) { return m_this; }; + /* Report the current version on the map object. */ + this._version = require('./version'); + /** * Draw a layer image to a canvas context. The layer's opacity and transform * are applied. This is used as part of making a screenshot. diff --git a/src/pixelmapLayer.js b/src/pixelmapLayer.js index 499245408d..cb4c17c823 100644 --- a/src/pixelmapLayer.js +++ b/src/pixelmapLayer.js @@ -96,6 +96,7 @@ var pixelmapLayer = function (arg) { this._init = function () { // Call super class init s_init.apply(m_this, arguments); + m_this.nearestPixel(true, true); const pixelmapArgs = {quadFeature: m_this.features()[0]}; if (arg.style) { diff --git a/src/quadFeature.js b/src/quadFeature.js index f8a135bebd..d5e42856c9 100644 --- a/src/quadFeature.js +++ b/src/quadFeature.js @@ -47,6 +47,10 @@ var feature = require('./feature'); * call `cacheUpdate` on the data item or for all data. * @property {geo.quadFeature.styleSpec} [style] Style object with default * style options. + * @property {boolean|number} [nearestPixel] If true, image quads are + * rendered with near-neighbor sampling. If false, with interpolated + * sampling. If a number, interpolate at that zoom level or below and + * nearest neighbor at that zoom level or above. */ /** @@ -106,6 +110,7 @@ var quadFeature = function (arg) { var m_this = this, s_init = this._init, m_cacheQuads, + m_nearestPixel = arg.nearestPixel, m_nextQuadId = 0, m_images = [], m_videos = [], @@ -677,6 +682,25 @@ var quadFeature = function (arg) { return null; }; + /** + * Get/Set nearestPixel value. + * + * @param {boolean|number} [val] If not specified, return the current value. + * If true, image quads are rendered with near-neighbor sampling. If + * false, with interpolated sampling. If a number, interpolate at that + * zoom level or below and nearest neighbor at that zoom level or above. + * @returns {boolean|number|this} + */ + this.nearestPixel = function (val) { + if (val === undefined) { + return m_nearestPixel; + } else { + m_nearestPixel = val; + m_this.modified(); + } + return m_this; + }; + /** * Initialize. * diff --git a/src/tileLayer.js b/src/tileLayer.js index 40fef5a5fa..6930a9af05 100644 --- a/src/tileLayer.js +++ b/src/tileLayer.js @@ -81,6 +81,10 @@ var featureLayer = require('./featureLayer'); * any tile layers. If specified, this uses the quad defaults, so this is a * ``geo.quadFeature.position`` object with, typically, an ``image`` property * added to it. The quad positions are in the map gcs coordinates. + * @property {boolean|number} [nearestPixel] If true, image quads are + * rendered with near-neighbor sampling. If false, with interpolated + * sampling. If a number, interpolate at that zoom level or below and + * nearest neighbor at that zoom level or above. */ /** @@ -228,6 +232,7 @@ var tileLayer = function (arg) { m_reference, m_exited, m_lastBaseQuad, + m_nearestPixel = arg.nearestPixel, m_this = this; // copy the options into a private variable @@ -1682,6 +1687,31 @@ var tileLayer = function (arg) { return m_this; }; + /** + * Get/Set nearestPixel value. + * + * @param {boolean|number} [val] If not specified, return the current value. + * If true, image quads are rendered with near-neighbor sampling. If + * false, with interpolated sampling. If a number, interpolate at that + * zoom level or below and nearest neighbor at that zoom level or above. + * @param {boolean} [skipUpdate] If specifying val and this value is truthy, + * don't update the layer or mark it as modified. + * @returns {boolean|number|this} + */ + this.nearestPixel = function (val, skipUpdate) { + if (val === undefined) { + return m_nearestPixel; + } + if (m_nearestPixel !== val) { + m_nearestPixel = val; + if (!skipUpdate) { + m_this.modified(); + m_this._update(); + } + } + return m_this; + }; + /** * Get/set the baseQuad. * diff --git a/src/webgl/pixelmapFeature.js b/src/webgl/pixelmapFeature.js index bd257295be..786973dbb7 100644 --- a/src/webgl/pixelmapFeature.js +++ b/src/webgl/pixelmapFeature.js @@ -134,7 +134,8 @@ var webgl_pixelmapFeature = function (arg) { m_quadFeature = m_this.layer().createFeature('quad', { selectionAPI: false, gcs: m_this.gcs(), - visible: m_this.visible(undefined, true) + visible: m_this.visible(undefined, true), + nearestPixel: true }); m_quadFeatureInit = false; } @@ -153,11 +154,6 @@ var webgl_pixelmapFeature = function (arg) { prog.addUniform(lutHeight); }; m_quadFeature._hookRenderImageQuads = (renderState, quads) => { - quads.forEach((quad) => { - if (quad.image && quad.texture && !quad.texture.nearestPixel()) { - quad.texture.setNearestPixel(true); - } - }); m_lookupTable.bind(renderState, quads); }; if (m_quadFeatureInit === false) { @@ -188,6 +184,7 @@ var webgl_pixelmapFeature = function (arg) { if (arg.quadFeature) { m_quadFeature = arg.quadFeature; + m_quadFeature.nearestPixel(true); } this._init(arg); return this; diff --git a/src/webgl/quadFeature.js b/src/webgl/quadFeature.js index 63ef66231f..cbcfa30646 100644 --- a/src/webgl/quadFeature.js +++ b/src/webgl/quadFeature.js @@ -2,6 +2,7 @@ var inherit = require('../inherit'); var registerFeature = require('../registry').registerFeature; var quadFeature = require('../quadFeature'); var timestamp = require('../timestamp'); +var util = require('../util'); let _memoryCheckLargestTested = 4096 * 4096; @@ -364,6 +365,18 @@ var webgl_quadFeature = function (arg) { cropsrc = {x0: 0, y0: 0, x1: 1, y1: 1}, quadcropsrc, w, h, quadw, quadh; + let nearestPixel = m_this.nearestPixel(); + if (nearestPixel !== undefined) { + if (nearestPixel !== true && util.isNonNullFinite(nearestPixel)) { + const curZoom = m_this.layer().map().zoom(); + nearestPixel = curZoom >= nearestPixel; + } + m_quads.imgQuads.forEach((quad) => { + if (quad.image && quad.texture && quad.texture.nearestPixel() !== nearestPixel) { + quad.texture.setNearestPixel(nearestPixel); + } + }); + } if (m_this._hookRenderImageQuads) { m_this._hookRenderImageQuads(renderState, m_quads.imgQuads); } diff --git a/src/webgl/tileLayer.js b/src/webgl/tileLayer.js index ad01f9275f..e0d17fb92c 100644 --- a/src/webgl/tileLayer.js +++ b/src/webgl/tileLayer.js @@ -171,6 +171,7 @@ var webgl_tileLayer = function () { */ this._update = function (request) { s_update.call(m_this, request); + m_quadFeature.nearestPixel(m_this.nearestPixel()); m_this._addBaseQuadToTiles(m_quadFeature, m_tiles); return m_this; }; diff --git a/tests/cases/osmLayer.js b/tests/cases/osmLayer.js index 5a307e799d..5dd11deb49 100644 --- a/tests/cases/osmLayer.js +++ b/tests/cases/osmLayer.js @@ -185,11 +185,25 @@ describe('geo.core.osmLayer', function () { it('destroy', destroy_map); }); describe('webgl', function () { + var layer; it('creation', function () { map = create_map(); - map.createLayer('osm', {renderer: 'webgl', url: '/testdata/white.jpg'}); + layer = map.createLayer('osm', {renderer: 'webgl', url: '/testdata/white.jpg'}); expect(map.node().find('.webgl-canvas').length).toBe(1); }); + it('nearestPixel', function () { + mockAnimationFrame(); + map.zoom(1); + stepAnimationFrame(); + expect(layer.nearestPixel()).toBe(undefined); + layer.nearestPixel(0).draw(); + stepAnimationFrame(); + expect(layer.nearestPixel()).toBe(0); + layer.nearestPixel(2).draw(); + stepAnimationFrame(); + expect(layer.nearestPixel()).toBe(2); + unmockAnimationFrame(); + }); it('destruction', destroy_map); }); describe('switch renderer', function () { diff --git a/tests/cases/tileLayer.js b/tests/cases/tileLayer.js index 371277f063..7a80c09fea 100644 --- a/tests/cases/tileLayer.js +++ b/tests/cases/tileLayer.js @@ -485,6 +485,22 @@ describe('geo.tileLayer', function () { // we don't test setting it here, as we have too much mocked to carry // through. The setting test is done in the osmLayer tests. }); + it('nearestPixel', function () { + var m = map(), layer; + opts.map = m; + layer = geo.tileLayer(opts); + expect(layer.nearestPixel()).toBe(undefined); + layer.nearestPixel(4, true); + expect(layer.nearestPixel()).toBe(4); + layer.nearestPixel(6, true); + expect(layer.nearestPixel()).toBe(6); + layer.nearestPixel(6, true); + expect(layer.nearestPixel()).toBe(6); + layer.nearestPixel(true, true); + expect(layer.nearestPixel()).toBe(true); + layer.nearestPixel(false, true); + expect(layer.nearestPixel()).toBe(false); + }); }); describe('Public utility methods', function () { describe('isValid', function () {