Merge pull request #8969 from tschaub/inverse-transforms

Create and use inverse pixel transforms during render
This commit is contained in:
Tim Schaub
2018-11-17 16:41:41 +01:00
committed by GitHub
11 changed files with 166 additions and 98 deletions

View File

@@ -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];

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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);
}
/**

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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<number>} */
@@ -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;
}

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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);
});
});