diff --git a/examples/magnify.js b/examples/magnify.js index 9193b2f1d2..f3822688ec 100644 --- a/examples/magnify.js +++ b/examples/magnify.js @@ -3,7 +3,7 @@ import View from '../src/ol/View.js'; import TileLayer from '../src/ol/layer/Tile.js'; import {fromLonLat} from '../src/ol/proj.js'; import BingMaps from '../src/ol/source/BingMaps.js'; -import {getPixelFromPixel} from '../src/ol/render.js'; +import {getRenderPixel} from '../src/ol/render.js'; const key = 'As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5'; @@ -51,8 +51,8 @@ container.addEventListener('mouseout', function() { // after rendering the layer, show an oversampled version around the pointer imagery.on('postrender', function(event) { if (mousePosition) { - const pixel = getPixelFromPixel(event, mousePosition); - const offset = getPixelFromPixel(event, [mousePosition[0] + radius, mousePosition[1]]); + const pixel = getRenderPixel(event, mousePosition); + const offset = getRenderPixel(event, [mousePosition[0] + radius, mousePosition[1]]); const half = Math.sqrt(Math.pow(offset[0] - pixel[0], 2) + Math.pow(offset[1] - pixel[1], 2)); const context = event.context; const centerX = pixel[0]; diff --git a/src/ol/render.js b/src/ol/render.js index 0f39bf8803..330e5aafa6 100644 --- a/src/ol/render.js +++ b/src/ol/render.js @@ -5,7 +5,6 @@ import {DEVICE_PIXEL_RATIO} from './has.js'; import { apply as applyTransform, create as createTransform, - invert as invertTransform, multiply as multiplyTransform, scale as scaleTransform } from './transform.js'; @@ -92,22 +91,22 @@ export function toContext(context, opt_options) { */ export function getVectorContext(event) { const frameState = event.frameState; - const transform = multiplyTransform(invertTransform(event.pixelTransform.slice()), frameState.coordinateToPixelTransform); + const transform = multiplyTransform(event.inversePixelTransform.slice(), frameState.coordinateToPixelTransform); return new CanvasImmediateRenderer( event.context, frameState.pixelRatio, frameState.extent, transform, frameState.viewState.rotation); } /** - * Gets the pixel of the event's canvas context from the map viewport's css pixel + * Gets the pixel of the event's canvas context from the map viewport's CSS pixel. * @param {import("./render/Event.js").default} event Render event. - * @param {import("./pixel.js").Pixel} pixel Css pixel relative to the top-left + * @param {import("./pixel.js").Pixel} pixel CSS pixel relative to the top-left * corner of the map viewport. * @returns {import("./pixel.js").Pixel} Pixel on the event's canvas context. * @api */ -export function getPixelFromPixel(event, pixel) { +export function getRenderPixel(event, pixel) { const result = pixel.slice(0); - applyTransform(invertTransform(event.pixelTransform.slice()), result); + applyTransform(event.inversePixelTransform.slice(), result); return result; } diff --git a/src/ol/render/Event.js b/src/ol/render/Event.js index 28caf9fdc2..36d09cf465 100644 --- a/src/ol/render/Event.js +++ b/src/ol/render/Event.js @@ -8,22 +8,23 @@ class RenderEvent extends Event { /** * @param {import("./EventType.js").default} type Type. - * @param {import("../transform.js").Transform=} opt_pixelTransform Transform. + * @param {import("../transform.js").Transform=} opt_inversePixelTransform Transform for + * CSS pixels to rendered pixels. * @param {import("../PluggableMap.js").FrameState=} opt_frameState Frame state. * @param {?CanvasRenderingContext2D=} opt_context Context. * @param {?import("../webgl/Helper.js").default=} opt_glContext WebGL Context. */ - constructor(type, opt_pixelTransform, opt_frameState, opt_context, opt_glContext) { + constructor(type, opt_inversePixelTransform, opt_frameState, opt_context, opt_glContext) { super(type); /** - * Transform from css pixels (relative to the top-left corner of the map viewport) - * to render pixel on this event's `context`. + * Transform from CSS pixels (relative to the top-left corner of the map viewport) + * to rendered pixels on this event's `context`. * @type {import("../transform.js").Transform|undefined} * @api */ - this.pixelTransform = opt_pixelTransform; + this.inversePixelTransform = opt_inversePixelTransform; /** * An object representing the current render frame state. diff --git a/src/ol/renderer/Map.js b/src/ol/renderer/Map.js index 281d1eb070..dcd4f96a69 100644 --- a/src/ol/renderer/Map.js +++ b/src/ol/renderer/Map.js @@ -9,7 +9,7 @@ import {getWidth} from '../extent.js'; import {TRUE} from '../functions.js'; import {visibleAtResolution} from '../layer/Layer.js'; import {shared as iconImageCache} from '../style/IconImageCache.js'; -import {compose as composeTransform, invert as invertTransform, setFromArray as transformSetFromArray} from '../transform.js'; +import {compose as composeTransform, makeInverse} from '../transform.js'; /** * @abstract @@ -66,8 +66,7 @@ class MapRenderer extends Disposable { -viewState.rotation, -viewState.center[0], -viewState.center[1]); - invertTransform( - transformSetFromArray(pixelToCoordinateTransform, coordinateToPixelTransform)); + makeInverse(pixelToCoordinateTransform, coordinateToPixelTransform); } /** diff --git a/src/ol/renderer/canvas/ImageLayer.js b/src/ol/renderer/canvas/ImageLayer.js index 405dd4ec05..4c4143a361 100644 --- a/src/ol/renderer/canvas/ImageLayer.js +++ b/src/ol/renderer/canvas/ImageLayer.js @@ -6,7 +6,7 @@ import ViewHint from '../../ViewHint.js'; import {containsExtent, intersects} from '../../extent.js'; import {getIntersection, isEmpty} from '../../extent.js'; import CanvasLayerRenderer from './Layer.js'; -import {compose as composeTransform, toString as transformToString} from '../../transform.js'; +import {compose as composeTransform, makeInverse, toString as transformToString} from '../../transform.js'; /** * @classdesc @@ -93,16 +93,17 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { width = height = size; } - const context = this.context; - const canvas = context.canvas; - - const pixelTransform = composeTransform(this.pixelTransform_, + // set forward and inverse pixel transforms + composeTransform(this.pixelTransform_, frameState.size[0] / 2, frameState.size[1] / 2, 1 / pixelRatio, 1 / pixelRatio, rotation, -width / 2, -height / 2 ); - const canvasTransform = transformToString(pixelTransform); + makeInverse(this.inversePixelTransform_, this.pixelTransform_); + + const context = this.context; + const canvas = context.canvas; if (canvas.width != width || canvas.height != height) { canvas.width = width; @@ -150,6 +151,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { canvas.style.opacity = opacity; } + const canvasTransform = transformToString(this.pixelTransform_); if (canvasTransform !== canvas.style.transform) { canvas.style.transform = canvasTransform; } diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index 212ee2d995..6417f7807a 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -7,7 +7,7 @@ import RenderEvent from '../../render/Event.js'; import RenderEventType from '../../render/EventType.js'; import {rotateAtOffset} from '../../render/canvas.js'; import LayerRenderer from '../Layer.js'; -import {invert as invertTransform, create as createTransform, apply as applyTransform, compose as composeTransform} from '../../transform.js'; +import {create as createTransform, apply as applyTransform, compose as composeTransform} from '../../transform.js'; /** * @abstract @@ -28,19 +28,29 @@ class CanvasLayerRenderer extends LayerRenderer { this.renderedResolution; /** - * A temporary transform. + * A temporary transform. The values in this transform should only be used in a + * function that sets the values. * @private * @type {import("../../transform.js").Transform} */ this.tempTransform_ = createTransform(); /** - * The transform for rendered pixels to viewport CSS pixels. + * The transform for rendered pixels to viewport CSS pixels. This transform must + * be set when rendering a frame and may be used by other functions after rendering. * @private * @type {import("../../transform.js").Transform} */ this.pixelTransform_ = createTransform(); + /** + * The transform for viewport CSS pixels to rendered pixels. This transform must + * be set when rendering a frame and may be used by other functions after rendering. + * @private + * @type {import("../../transform.js").Transform} + */ + this.inversePixelTransform_ = createTransform(); + /** * @protected * @type {CanvasRenderingContext2D} @@ -102,8 +112,7 @@ class CanvasLayerRenderer extends LayerRenderer { applyTransform(frameState.coordinateToPixelTransform, bottomRight); applyTransform(frameState.coordinateToPixelTransform, bottomLeft); - const inverted = invertTransform(this.pixelTransform_.slice()); - + const inverted = this.inversePixelTransform_; applyTransform(inverted, topLeft); applyTransform(inverted, topRight); applyTransform(inverted, bottomRight); @@ -122,46 +131,32 @@ class CanvasLayerRenderer extends LayerRenderer { * @param {import("../../render/EventType.js").default} type Event type. * @param {CanvasRenderingContext2D} context Context. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. - * @param {import("../../transform.js").Transform} pixelTransform Transform. * @private */ - dispatchComposeEvent_(type, context, frameState, pixelTransform) { + dispatchRenderEvent_(type, context, frameState) { const layer = this.getLayer(); if (layer.hasListener(type)) { - const composeEvent = new RenderEvent(type, pixelTransform, frameState, - context, null); - layer.dispatchEvent(composeEvent); + const event = new RenderEvent(type, this.inversePixelTransform_, frameState, context, null); + layer.dispatchEvent(event); } } /** * @param {CanvasRenderingContext2D} context Context. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. - * @param {import("../../transform.js").Transform=} opt_transform Transform. * @protected */ - preRender(context, frameState, opt_transform) { - this.dispatchComposeEvent_(RenderEventType.PRERENDER, context, frameState, opt_transform); + preRender(context, frameState) { + this.dispatchRenderEvent_(RenderEventType.PRERENDER, context, frameState); } /** * @param {CanvasRenderingContext2D} context Context. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. - * @param {import("../../transform.js").Transform=} opt_transform Transform. * @protected */ - postRender(context, frameState, opt_transform) { - this.dispatchComposeEvent_(RenderEventType.POSTRENDER, context, frameState, opt_transform); - } - - /** - * @param {CanvasRenderingContext2D} context Context. - * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. - * @param {import("../../transform.js").Transform=} opt_transform Transform. - * @protected - */ - dispatchRenderEvent(context, frameState, opt_transform) { - this.dispatchComposeEvent_(RenderEventType.RENDER, context, frameState, opt_transform); + postRender(context, frameState) { + this.dispatchRenderEvent_(RenderEventType.POSTRENDER, context, frameState); } /** @@ -194,7 +189,7 @@ class CanvasLayerRenderer extends LayerRenderer { * returned, and empty array will be returned. */ getDataAtPixel(pixel, frameState, hitTolerance) { - const renderPixel = applyTransform(invertTransform(this.pixelTransform_.slice()), pixel.slice()); + const renderPixel = applyTransform(this.inversePixelTransform_, pixel.slice()); const context = this.context; let data; diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 3c8c56a8a2..8f389cf15b 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -6,7 +6,7 @@ import TileRange from '../../TileRange.js'; import TileState from '../../TileState.js'; import {createEmpty, getIntersection, getTopLeft} from '../../extent.js'; import CanvasLayerRenderer from './Layer.js'; -import {compose as composeTransform, toString as transformToString} from '../../transform.js'; +import {compose as composeTransform, makeInverse, toString as transformToString} from '../../transform.js'; /** * @classdesc @@ -222,15 +222,18 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { } } + const canvas = context.canvas; const canvasScale = tileResolution / frameState.viewState.resolution / tilePixelRatio; - const pixelTransform = composeTransform(this.pixelTransform_, + + // set forward and inverse pixel transforms + composeTransform(this.pixelTransform_, frameState.size[0] / 2, frameState.size[1] / 2, canvasScale, canvasScale, rotation, -width / 2, -height / 2 ); - const canvasTransform = transformToString(pixelTransform); + makeInverse(this.inversePixelTransform_, this.pixelTransform_); if (canvas.width != width || canvas.height != height) { canvas.width = width; @@ -243,7 +246,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { this.clipUnrotated(context, frameState, layerState.extent); } - this.preRender(context, frameState, pixelTransform); + this.preRender(context, frameState); this.renderedTiles.length = 0; /** @type {Array} */ @@ -291,7 +294,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { projection, extent, z, tileLayer.getPreload()); this.scheduleExpireCache(frameState, tileSource); - this.postRender(context, frameState, pixelTransform); + this.postRender(context, frameState); if (layerState.extent) { context.restore(); @@ -302,6 +305,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { canvas.style.opacity = opacity; } + const canvasTransform = transformToString(this.pixelTransform_); if (canvasTransform !== canvas.style.transform) { canvas.style.transform = canvasTransform; } diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index 78ffb866d4..452c8b82a6 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -7,13 +7,12 @@ import {listen, unlisten} from '../../events.js'; import EventType from '../../events/EventType.js'; import rbush from 'rbush'; import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js'; -import RenderEventType from '../../render/EventType.js'; import {labelCache} from '../../render/canvas.js'; import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js'; import InstructionsGroupExecutor from '../../render/canvas/ExecutorGroup.js'; import CanvasLayerRenderer from './Layer.js'; import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js'; -import {toString as transformToString} from '../../transform.js'; +import {toString as transformToString, makeScale, makeInverse} from '../../transform.js'; /** * @classdesc @@ -105,8 +104,9 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { const pixelRatio = frameState.pixelRatio; - // a scale transform (we could add a createScale function in ol/transform) - const pixelTransform = [1 / pixelRatio, 0, 0, 1 / pixelRatio, 0, 0]; + // set forward and inverse pixel transforms + makeScale(this.pixelTransform_, 1 / pixelRatio, 1 / pixelRatio); + makeInverse(this.inversePixelTransform_, this.pixelTransform_); // resize and clear const width = Math.round(frameState.size[0] * pixelRatio); @@ -114,7 +114,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { if (canvas.width != width || canvas.height != height) { canvas.width = width; canvas.height = height; - const canvasTransform = transformToString(pixelTransform); + const canvasTransform = transformToString(this.pixelTransform_); if (canvas.style.transform !== canvasTransform) { canvas.style.transform = canvasTransform; } @@ -122,7 +122,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { context.clearRect(0, 0, width, height); } - this.preRender(context, frameState, pixelTransform); + this.preRender(context, frameState); const extent = frameState.extent; const viewState = frameState.viewState; @@ -173,15 +173,11 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { } } - if (this.getLayer().hasListener(RenderEventType.RENDER)) { - this.dispatchRenderEvent(context, frameState, pixelTransform); - } - if (clipped) { context.restore(); } - this.postRender(context, frameState, pixelTransform); + this.postRender(context, frameState); const opacity = layerState.opacity; if (opacity !== canvas.style.opacity) { diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 8d58fbafec..b9a8a28e0d 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -22,11 +22,12 @@ import { apply as applyTransform, create as createTransform, compose as composeTransform, - invert as invertTransform, reset as resetTransform, scale as scaleTransform, translate as translateTransform, - toString as transformToString + toString as transformToString, + makeScale, + makeInverse } from '../../transform.js'; import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js'; @@ -98,6 +99,13 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { */ this.overlayPixelTransform_ = createTransform(); + /** + * The transform for viewport CSS pixels to rendered pixels for the overlay canvas. + * @private + * @type {import("../../transform.js").Transform} + */ + this.inverseOverlayPixelTransform_ = createTransform(); + /** * Declutter tree. * @private @@ -384,12 +392,14 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { const rotation = frameState.viewState.rotation; const size = frameState.size; + // set forward and inverse pixel transforms + makeScale(this.overlayPixelTransform_, 1 / pixelRatio, 1 / pixelRatio); + makeInverse(this.inverseOverlayPixelTransform_, this.overlayPixelTransform_); + // resize and clear const canvas = context.canvas; const width = Math.round(size[0] * pixelRatio); const height = Math.round(size[1] * pixelRatio); - this.overlayPixelTransform_[0] = 1 / pixelRatio; - this.overlayPixelTransform_[3] = 1 / pixelRatio; if (canvas.width != width || canvas.height != height) { canvas.width = width; canvas.height = height; @@ -544,7 +554,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { return data; } - const renderPixel = applyTransform(invertTransform(this.overlayPixelTransform_.slice()), pixel.slice()); + const renderPixel = applyTransform(this.inverseOverlayPixelTransform_, pixel.slice()); const context = this.overlayContext_; try { diff --git a/src/ol/transform.js b/src/ol/transform.js index cbb19c3c39..c7511480fb 100644 --- a/src/ol/transform.js +++ b/src/ol/transform.js @@ -162,6 +162,16 @@ export function scale(transform, x, y) { return multiply(transform, set(tmp_, x, 0, 0, y, 0, 0)); } +/** + * Creates a scale transform. + * @param {!Transform} target Transform to overwrite. + * @param {number} x Scale factor x. + * @param {number} y Scale factor y. + * @return {!Transform} The scale transform. + */ +export function makeScale(target, x, y) { + return set(target, x, 0, 0, y, 0, 0); +} /** * Applies translation to the given transform. @@ -203,30 +213,40 @@ export function compose(transform, dx1, dy1, sx, sy, angle, dx2, dy2) { /** * Invert the given transform. - * @param {!Transform} transform Transform. - * @return {!Transform} Inverse of the transform. + * @param {!Transform} source The source transform to invert. + * @return {!Transform} The inverted (source) transform. */ -export function invert(transform) { - const det = determinant(transform); - assert(det !== 0, 32); // Transformation matrix cannot be inverted - - const a = transform[0]; - const b = transform[1]; - const c = transform[2]; - const d = transform[3]; - const e = transform[4]; - const f = transform[5]; - - transform[0] = d / det; - transform[1] = -b / det; - transform[2] = -c / det; - transform[3] = a / det; - transform[4] = (c * f - d * e) / det; - transform[5] = -(a * f - b * e) / det; - - return transform; +export function invert(source) { + return makeInverse(source, source); } +/** + * Invert the given transform. + * @param {!Transform} target Transform to be set as the inverse of + * the source transform. + * @param {!Transform} source The source transform to invert. + * @return {!Transform} The inverted (target) transform. + */ +export function makeInverse(target, source) { + const det = determinant(source); + assert(det !== 0, 32); // Transformation matrix cannot be inverted + + const a = source[0]; + const b = source[1]; + const c = source[2]; + const d = source[3]; + const e = source[4]; + const f = source[5]; + + target[0] = d / det; + target[1] = -b / det; + target[2] = -c / det; + target[3] = a / det; + target[4] = (c * f - d * e) / det; + target[5] = -(a * f - b * e) / det; + + return target; +} /** * Returns the determinant of the given matrix. diff --git a/test/spec/ol/transform.test.js b/test/spec/ol/transform.test.js index 13b5d91846..5a8308ad13 100644 --- a/test/spec/ol/transform.test.js +++ b/test/spec/ol/transform.test.js @@ -5,10 +5,12 @@ import { setFromArray, translate, scale, + makeScale, rotate, multiply, compose, invert, + makeInverse, apply } from '../../../src/ol/transform.js'; @@ -68,6 +70,20 @@ describe('ol.transform', function() { }); }); + describe('makeScale()', function() { + it('creates a scale transform', function() { + const target = create(); + makeScale(target, 2, 3); + expect(target).to.eql([2, 0, 0, 3, 0, 0]); + }); + + it('returns the target', function() { + const target = create(); + const transform = makeScale(target, 2, 3); + expect(transform).to.be(target); + }); + }); + describe('rotate()', function() { it('applies rotation to a transform', function() { const transform = create(); @@ -110,13 +126,39 @@ describe('ol.transform', function() { describe('invert()', function() { it('inverts a transform', function() { - let transform = [1, 0, 1, 0, 1, 0]; - expect(function() { - invert(transform); - }).to.throwException(); - transform = [1, 1, 1, 2, 2, 0]; + const transform = [1, 1, 1, 2, 2, 0]; expect(invert(transform)).to.eql([2, -1, -1, 1, -4, 2]); - expect(transform).to.eql([2, -1, -1, 1, -4, 2]); + }); + + it('throws if the transform cannot be inverted', function() { + const indeterminant = [1, 0, 1, 0, 1, 0]; + expect(function() { + invert(indeterminant); + }).to.throwException(); + }); + + it('modifies the source', function() { + const source = [1, 1, 1, 2, 2, 0]; + const inverted = invert(source); + expect(inverted).to.eql([2, -1, -1, 1, -4, 2]); + expect(source).to.be(inverted); + }); + }); + + describe('makeInverse()', function() { + it('makes the target the inverse of the source', function() { + const source = [1, 1, 1, 2, 2, 0]; + const target = [1, 0, 0, 1, 0, 0]; + makeInverse(target, source); + expect(source).to.eql([1, 1, 1, 2, 2, 0]); + expect(target).to.eql([2, -1, -1, 1, -4, 2]); + }); + + it('returns the target', function() { + const source = [1, 1, 1, 2, 2, 0]; + const target = [1, 0, 0, 1, 0, 0]; + const inverted = makeInverse(target, source); + expect(target).to.be(inverted); }); });