Merge pull request #13823 from ahocevar/reduce-canvas-footprint
Reduce canvas memory footprint for better iOS stability
@@ -2,7 +2,7 @@
|
|||||||
* @module ol/VectorRenderTile
|
* @module ol/VectorRenderTile
|
||||||
*/
|
*/
|
||||||
import Tile from './Tile.js';
|
import Tile from './Tile.js';
|
||||||
import {createCanvasContext2D} from './dom.js';
|
import {createCanvasContext2D, releaseCanvas} from './dom.js';
|
||||||
import {getUid} from './util.js';
|
import {getUid} from './util.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,7 +154,9 @@ class VectorRenderTile extends Tile {
|
|||||||
*/
|
*/
|
||||||
release() {
|
release() {
|
||||||
for (const key in this.context_) {
|
for (const key in this.context_) {
|
||||||
canvasPool.push(this.context_[key].canvas);
|
const context = this.context_[key];
|
||||||
|
releaseCanvas(context);
|
||||||
|
canvasPool.push(context.canvas);
|
||||||
delete this.context_[key];
|
delete this.context_[key];
|
||||||
}
|
}
|
||||||
super.release();
|
super.release();
|
||||||
|
|||||||
@@ -114,11 +114,3 @@ export const getFontParameters = function (fontSpec) {
|
|||||||
style.families = style.family.split(/,\s?/);
|
style.families = style.family.split(/,\s?/);
|
||||||
return style;
|
return style;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} opacity Opacity (0..1).
|
|
||||||
* @return {string} CSS opacity.
|
|
||||||
*/
|
|
||||||
export function cssOpacity(opacity) {
|
|
||||||
return opacity === 1 ? '' : String(Math.round(opacity * 100) / 100);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -40,6 +40,18 @@ export function createCanvasContext2D(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases canvas memory to avoid exceeding memory limits in Safari.
|
||||||
|
* See https://pqina.nl/blog/total-canvas-memory-use-exceeds-the-maximum-limit/
|
||||||
|
* @param {CanvasRenderingContext2D} context Context.
|
||||||
|
*/
|
||||||
|
export function releaseCanvas(context) {
|
||||||
|
const canvas = context.canvas;
|
||||||
|
canvas.width = 1;
|
||||||
|
canvas.height = 1;
|
||||||
|
context.clearRect(0, 0, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current computed width for the given element including margin,
|
* Get the current computed width for the given element including margin,
|
||||||
* padding and border.
|
* padding and border.
|
||||||
|
|||||||
@@ -535,6 +535,29 @@ export function getForViewAndSize(
|
|||||||
size,
|
size,
|
||||||
opt_extent
|
opt_extent
|
||||||
) {
|
) {
|
||||||
|
const [x0, y0, x1, y1, x2, y2, x3, y3] = getRotatedViewport(
|
||||||
|
center,
|
||||||
|
resolution,
|
||||||
|
rotation,
|
||||||
|
size
|
||||||
|
);
|
||||||
|
return createOrUpdate(
|
||||||
|
Math.min(x0, x1, x2, x3),
|
||||||
|
Math.min(y0, y1, y2, y3),
|
||||||
|
Math.max(x0, x1, x2, x3),
|
||||||
|
Math.max(y0, y1, y2, y3),
|
||||||
|
opt_extent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("./coordinate.js").Coordinate} center Center.
|
||||||
|
* @param {number} resolution Resolution.
|
||||||
|
* @param {number} rotation Rotation.
|
||||||
|
* @param {import("./size.js").Size} size Size.
|
||||||
|
* @return {Array<number>} Linear ring representing the viewport.
|
||||||
|
*/
|
||||||
|
export function getRotatedViewport(center, resolution, rotation, size) {
|
||||||
const dx = (resolution * size[0]) / 2;
|
const dx = (resolution * size[0]) / 2;
|
||||||
const dy = (resolution * size[1]) / 2;
|
const dy = (resolution * size[1]) / 2;
|
||||||
const cosRotation = Math.cos(rotation);
|
const cosRotation = Math.cos(rotation);
|
||||||
@@ -545,21 +568,18 @@ export function getForViewAndSize(
|
|||||||
const ySin = dy * sinRotation;
|
const ySin = dy * sinRotation;
|
||||||
const x = center[0];
|
const x = center[0];
|
||||||
const y = center[1];
|
const y = center[1];
|
||||||
const x0 = x - xCos + ySin;
|
return [
|
||||||
const x1 = x - xCos - ySin;
|
x - xCos + ySin,
|
||||||
const x2 = x + xCos - ySin;
|
y - xSin - yCos,
|
||||||
const x3 = x + xCos + ySin;
|
x - xCos - ySin,
|
||||||
const y0 = y - xSin - yCos;
|
y - xSin + yCos,
|
||||||
const y1 = y - xSin + yCos;
|
x + xCos - ySin,
|
||||||
const y2 = y + xSin + yCos;
|
y + xSin + yCos,
|
||||||
const y3 = y + xSin - yCos;
|
x + xCos + ySin,
|
||||||
return createOrUpdate(
|
y + xSin - yCos,
|
||||||
Math.min(x0, x1, x2, x3),
|
x - xCos + ySin,
|
||||||
Math.min(y0, y1, y2, y3),
|
y - xSin - yCos,
|
||||||
Math.max(x0, x1, x2, x3),
|
];
|
||||||
Math.max(y0, y1, y2, y3),
|
|
||||||
opt_extent
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -168,18 +168,15 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
|
|||||||
const viewState = frameState.viewState;
|
const viewState = frameState.viewState;
|
||||||
const viewCenter = viewState.center;
|
const viewCenter = viewState.center;
|
||||||
const viewResolution = viewState.resolution;
|
const viewResolution = viewState.resolution;
|
||||||
const size = frameState.size;
|
|
||||||
const scale =
|
const scale =
|
||||||
(pixelRatio * imageResolution) / (viewResolution * imagePixelRatio);
|
(pixelRatio * imageResolution) / (viewResolution * imagePixelRatio);
|
||||||
|
|
||||||
let width = Math.round(size[0] * pixelRatio);
|
const extent = frameState.extent;
|
||||||
let height = Math.round(size[1] * pixelRatio);
|
const resolution = viewState.resolution;
|
||||||
const rotation = viewState.rotation;
|
const rotation = viewState.rotation;
|
||||||
if (rotation) {
|
// desired dimensions of the canvas in pixels
|
||||||
const size = Math.round(Math.sqrt(width * width + height * height));
|
const width = Math.round((getWidth(extent) / resolution) * pixelRatio);
|
||||||
width = size;
|
const height = Math.round((getHeight(extent) / resolution) * pixelRatio);
|
||||||
height = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set forward and inverse pixel transforms
|
// set forward and inverse pixel transforms
|
||||||
composeTransform(
|
composeTransform(
|
||||||
@@ -196,12 +193,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
|
|||||||
|
|
||||||
const canvasTransform = toTransformString(this.pixelTransform);
|
const canvasTransform = toTransformString(this.pixelTransform);
|
||||||
|
|
||||||
this.useContainer(
|
this.useContainer(target, canvasTransform, this.getBackground(frameState));
|
||||||
target,
|
|
||||||
canvasTransform,
|
|
||||||
layerState.opacity,
|
|
||||||
this.getBackground(frameState)
|
|
||||||
);
|
|
||||||
|
|
||||||
const context = this.context;
|
const context = this.context;
|
||||||
const canvas = context.canvas;
|
const canvas = context.canvas;
|
||||||
@@ -260,17 +252,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
|
|||||||
previousAlpha = context.globalAlpha;
|
previousAlpha = context.globalAlpha;
|
||||||
context.globalAlpha = opacity;
|
context.globalAlpha = opacity;
|
||||||
}
|
}
|
||||||
context.drawImage(
|
context.drawImage(img, 0, 0, +img.width, +img.height, dx, dy, dw, dh);
|
||||||
img,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
+img.width,
|
|
||||||
+img.height,
|
|
||||||
Math.round(dx),
|
|
||||||
Math.round(dy),
|
|
||||||
Math.round(dw),
|
|
||||||
Math.round(dh)
|
|
||||||
);
|
|
||||||
if (opacity !== 1) {
|
if (opacity !== 1) {
|
||||||
context.globalAlpha = previousAlpha;
|
context.globalAlpha = previousAlpha;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ import {
|
|||||||
import {createCanvasContext2D} from '../../dom.js';
|
import {createCanvasContext2D} from '../../dom.js';
|
||||||
import {equals} from '../../array.js';
|
import {equals} from '../../array.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<HTMLCanvasElement>}
|
||||||
|
*/
|
||||||
|
export const canvasPool = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {CanvasRenderingContext2D}
|
* @type {CanvasRenderingContext2D}
|
||||||
*/
|
*/
|
||||||
@@ -143,17 +148,14 @@ class CanvasLayerRenderer extends LayerRenderer {
|
|||||||
* Get a rendering container from an existing target, if compatible.
|
* Get a rendering container from an existing target, if compatible.
|
||||||
* @param {HTMLElement} target Potential render target.
|
* @param {HTMLElement} target Potential render target.
|
||||||
* @param {string} transform CSS Transform.
|
* @param {string} transform CSS Transform.
|
||||||
* @param {number} opacity Opacity.
|
|
||||||
* @param {string} [opt_backgroundColor] Background color.
|
* @param {string} [opt_backgroundColor] Background color.
|
||||||
*/
|
*/
|
||||||
useContainer(target, transform, opacity, opt_backgroundColor) {
|
useContainer(target, transform, opt_backgroundColor) {
|
||||||
const layerClassName = this.getLayer().getClassName();
|
const layerClassName = this.getLayer().getClassName();
|
||||||
let container, context;
|
let container, context;
|
||||||
if (
|
if (
|
||||||
target &&
|
target &&
|
||||||
target.className === layerClassName &&
|
target.className === layerClassName &&
|
||||||
target.style.opacity === '' &&
|
|
||||||
opacity === 1 &&
|
|
||||||
(!opt_backgroundColor ||
|
(!opt_backgroundColor ||
|
||||||
(target &&
|
(target &&
|
||||||
target.style.backgroundColor &&
|
target.style.backgroundColor &&
|
||||||
|
|||||||
@@ -18,11 +18,13 @@ import {
|
|||||||
containsCoordinate,
|
containsCoordinate,
|
||||||
createEmpty,
|
createEmpty,
|
||||||
equals,
|
equals,
|
||||||
|
getHeight,
|
||||||
getIntersection,
|
getIntersection,
|
||||||
|
getRotatedViewport,
|
||||||
getTopLeft,
|
getTopLeft,
|
||||||
|
getWidth,
|
||||||
intersects,
|
intersects,
|
||||||
} from '../../extent.js';
|
} from '../../extent.js';
|
||||||
import {cssOpacity} from '../../css.js';
|
|
||||||
import {fromUserExtent} from '../../proj.js';
|
import {fromUserExtent} from '../../proj.js';
|
||||||
import {getUid} from '../../util.js';
|
import {getUid} from '../../util.js';
|
||||||
import {numberSafeCompareFunction} from '../../array.js';
|
import {numberSafeCompareFunction} from '../../array.js';
|
||||||
@@ -263,6 +265,12 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
const tileResolution = tileGrid.getResolution(z);
|
const tileResolution = tileGrid.getResolution(z);
|
||||||
|
|
||||||
let extent = frameState.extent;
|
let extent = frameState.extent;
|
||||||
|
const resolution = frameState.viewState.resolution;
|
||||||
|
const tilePixelRatio = tileSource.getTilePixelRatio(pixelRatio);
|
||||||
|
// desired dimensions of the canvas in pixels
|
||||||
|
const width = Math.round((getWidth(extent) / resolution) * pixelRatio);
|
||||||
|
const height = Math.round((getHeight(extent) / resolution) * pixelRatio);
|
||||||
|
|
||||||
const layerExtent =
|
const layerExtent =
|
||||||
layerState.extent && fromUserExtent(layerState.extent, projection);
|
layerState.extent && fromUserExtent(layerState.extent, projection);
|
||||||
if (layerExtent) {
|
if (layerExtent) {
|
||||||
@@ -272,18 +280,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tilePixelRatio = tileSource.getTilePixelRatio(pixelRatio);
|
|
||||||
|
|
||||||
// desired dimensions of the canvas in pixels
|
|
||||||
let width = Math.round(frameState.size[0] * tilePixelRatio);
|
|
||||||
let height = Math.round(frameState.size[1] * tilePixelRatio);
|
|
||||||
|
|
||||||
if (rotation) {
|
|
||||||
const size = Math.round(Math.sqrt(width * width + height * height));
|
|
||||||
width = size;
|
|
||||||
height = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dx = (tileResolution * width) / 2 / tilePixelRatio;
|
const dx = (tileResolution * width) / 2 / tilePixelRatio;
|
||||||
const dy = (tileResolution * height) / 2 / tilePixelRatio;
|
const dy = (tileResolution * height) / 2 / tilePixelRatio;
|
||||||
const canvasExtent = [
|
const canvasExtent = [
|
||||||
@@ -310,14 +306,33 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
const tmpExtent = this.tmpExtent;
|
const tmpExtent = this.tmpExtent;
|
||||||
const tmpTileRange = this.tmpTileRange_;
|
const tmpTileRange = this.tmpTileRange_;
|
||||||
this.newTiles_ = false;
|
this.newTiles_ = false;
|
||||||
|
const viewport = rotation
|
||||||
|
? getRotatedViewport(
|
||||||
|
viewState.center,
|
||||||
|
resolution,
|
||||||
|
rotation,
|
||||||
|
frameState.size
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
for (let x = tileRange.minX; x <= tileRange.maxX; ++x) {
|
for (let x = tileRange.minX; x <= tileRange.maxX; ++x) {
|
||||||
for (let y = tileRange.minY; y <= tileRange.maxY; ++y) {
|
for (let y = tileRange.minY; y <= tileRange.maxY; ++y) {
|
||||||
|
if (
|
||||||
|
rotation &&
|
||||||
|
!tileGrid.tileCoordIntersectsViewport([z, x, y], viewport)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const tile = this.getTile(z, x, y, frameState);
|
const tile = this.getTile(z, x, y, frameState);
|
||||||
if (this.isDrawableTile(tile)) {
|
if (this.isDrawableTile(tile)) {
|
||||||
const uid = getUid(this);
|
const uid = getUid(this);
|
||||||
if (tile.getState() == TileState.LOADED) {
|
if (tile.getState() == TileState.LOADED) {
|
||||||
tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
|
tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
|
||||||
const inTransition = tile.inTransition(uid);
|
let inTransition = tile.inTransition(uid);
|
||||||
|
if (inTransition && layerState.opacity !== 1) {
|
||||||
|
// Skipping transition when layer is not fully opaque avoids visual artifacts.
|
||||||
|
tile.endTransition(uid);
|
||||||
|
inTransition = false;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
!this.newTiles_ &&
|
!this.newTiles_ &&
|
||||||
(inTransition || this.renderedTiles.indexOf(tile) === -1)
|
(inTransition || this.renderedTiles.indexOf(tile) === -1)
|
||||||
@@ -352,15 +367,16 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const canvasScale = tileResolution / viewResolution;
|
const canvasScale =
|
||||||
|
((tileResolution / viewResolution) * pixelRatio) / tilePixelRatio;
|
||||||
|
|
||||||
// set forward and inverse pixel transforms
|
// set forward and inverse pixel transforms
|
||||||
composeTransform(
|
composeTransform(
|
||||||
this.pixelTransform,
|
this.pixelTransform,
|
||||||
frameState.size[0] / 2,
|
frameState.size[0] / 2,
|
||||||
frameState.size[1] / 2,
|
frameState.size[1] / 2,
|
||||||
1 / tilePixelRatio,
|
1 / pixelRatio,
|
||||||
1 / tilePixelRatio,
|
1 / pixelRatio,
|
||||||
rotation,
|
rotation,
|
||||||
-width / 2,
|
-width / 2,
|
||||||
-height / 2
|
-height / 2
|
||||||
@@ -368,12 +384,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
|
|
||||||
const canvasTransform = toTransformString(this.pixelTransform);
|
const canvasTransform = toTransformString(this.pixelTransform);
|
||||||
|
|
||||||
this.useContainer(
|
this.useContainer(target, canvasTransform, this.getBackground(frameState));
|
||||||
target,
|
|
||||||
canvasTransform,
|
|
||||||
layerState.opacity,
|
|
||||||
this.getBackground(frameState)
|
|
||||||
);
|
|
||||||
const context = this.context;
|
const context = this.context;
|
||||||
const canvas = context.canvas;
|
const canvas = context.canvas;
|
||||||
|
|
||||||
@@ -559,11 +570,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
if (canvasTransform !== canvas.style.transform) {
|
if (canvasTransform !== canvas.style.transform) {
|
||||||
canvas.style.transform = canvasTransform;
|
canvas.style.transform = canvasTransform;
|
||||||
}
|
}
|
||||||
const opacity = cssOpacity(layerState.opacity);
|
|
||||||
const container = this.container;
|
|
||||||
if (opacity !== container.style.opacity) {
|
|
||||||
container.style.opacity = opacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.container;
|
return this.container;
|
||||||
}
|
}
|
||||||
@@ -584,7 +590,10 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const uid = getUid(this);
|
const uid = getUid(this);
|
||||||
const alpha = transition ? tile.getAlpha(uid, frameState.time) : 1;
|
const layerState = frameState.layerStatesArray[frameState.layerIndex];
|
||||||
|
const alpha =
|
||||||
|
layerState.opacity *
|
||||||
|
(transition ? tile.getAlpha(uid, frameState.time) : 1);
|
||||||
const alphaChanged = alpha !== this.context.globalAlpha;
|
const alphaChanged = alpha !== this.context.globalAlpha;
|
||||||
if (alphaChanged) {
|
if (alphaChanged) {
|
||||||
this.context.save();
|
this.context.save();
|
||||||
@@ -605,7 +614,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
if (alphaChanged) {
|
if (alphaChanged) {
|
||||||
this.context.restore();
|
this.context.restore();
|
||||||
}
|
}
|
||||||
if (alpha !== 1) {
|
if (alpha !== layerState.opacity) {
|
||||||
frameState.animate = true;
|
frameState.animate = true;
|
||||||
} else if (transition) {
|
} else if (transition) {
|
||||||
tile.endTransition(uid);
|
tile.endTransition(uid);
|
||||||
@@ -711,6 +720,15 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
const wantedTiles = frameState.wantedTiles[tileSourceKey];
|
const wantedTiles = frameState.wantedTiles[tileSourceKey];
|
||||||
const tileQueue = frameState.tileQueue;
|
const tileQueue = frameState.tileQueue;
|
||||||
const minZoom = tileGrid.getMinZoom();
|
const minZoom = tileGrid.getMinZoom();
|
||||||
|
const rotation = frameState.viewState.rotation;
|
||||||
|
const viewport = rotation
|
||||||
|
? getRotatedViewport(
|
||||||
|
frameState.viewState.center,
|
||||||
|
frameState.viewState.resolution,
|
||||||
|
rotation,
|
||||||
|
frameState.size
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
let tileCount = 0;
|
let tileCount = 0;
|
||||||
let tile, tileRange, tileResolution, x, y, z;
|
let tile, tileRange, tileResolution, x, y, z;
|
||||||
for (z = minZoom; z <= currentZ; ++z) {
|
for (z = minZoom; z <= currentZ; ++z) {
|
||||||
@@ -718,6 +736,12 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
tileResolution = tileGrid.getResolution(z);
|
tileResolution = tileGrid.getResolution(z);
|
||||||
for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
|
for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
|
||||||
for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
|
for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
|
||||||
|
if (
|
||||||
|
rotation &&
|
||||||
|
!tileGrid.tileCoordIntersectsViewport([z, x, y], viewport)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (currentZ - z <= preload) {
|
if (currentZ - z <= preload) {
|
||||||
++tileCount;
|
++tileCount;
|
||||||
tile = tileSource.getTile(z, x, y, pixelRatio, projection);
|
tile = tileSource.getTile(z, x, y, pixelRatio, projection);
|
||||||
|
|||||||
@@ -105,8 +105,11 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
|
|||||||
!hints[ViewHint.INTERACTING] &&
|
!hints[ViewHint.INTERACTING] &&
|
||||||
!isEmpty(renderedExtent)
|
!isEmpty(renderedExtent)
|
||||||
) {
|
) {
|
||||||
vectorRenderer.useContainer(null, null, 1);
|
vectorRenderer.useContainer(null, null);
|
||||||
const context = vectorRenderer.context;
|
const context = vectorRenderer.context;
|
||||||
|
const layerState = frameState.layerStatesArray[frameState.layerIndex];
|
||||||
|
context.globalAlpha = layerState.opacity;
|
||||||
|
const imageLayerState = assign({}, layerState, {opacity: 1});
|
||||||
const imageFrameState =
|
const imageFrameState =
|
||||||
/** @type {import("../../PluggableMap.js").FrameState} */ (
|
/** @type {import("../../PluggableMap.js").FrameState} */ (
|
||||||
assign({}, frameState, {
|
assign({}, frameState, {
|
||||||
@@ -118,6 +121,8 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
|
|||||||
rotation: 0,
|
rotation: 0,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
layerStatesArray: [imageLayerState],
|
||||||
|
layerIndex: 0,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
let emptyImage = true;
|
let emptyImage = true;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @module ol/renderer/canvas/VectorLayer
|
* @module ol/renderer/canvas/VectorLayer
|
||||||
*/
|
*/
|
||||||
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
|
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
|
||||||
import CanvasLayerRenderer from './Layer.js';
|
import CanvasLayerRenderer, {canvasPool} from './Layer.js';
|
||||||
import ExecutorGroup from '../../render/canvas/ExecutorGroup.js';
|
import ExecutorGroup from '../../render/canvas/ExecutorGroup.js';
|
||||||
import ViewHint from '../../ViewHint.js';
|
import ViewHint from '../../ViewHint.js';
|
||||||
import {
|
import {
|
||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
intersects as intersectsExtent,
|
intersects as intersectsExtent,
|
||||||
wrapX as wrapExtentX,
|
wrapX as wrapExtentX,
|
||||||
} from '../../extent.js';
|
} from '../../extent.js';
|
||||||
import {cssOpacity} from '../../css.js';
|
import {createCanvasContext2D, releaseCanvas} from '../../dom.js';
|
||||||
import {
|
import {
|
||||||
defaultOrder as defaultRenderOrder,
|
defaultOrder as defaultRenderOrder,
|
||||||
getTolerance as getRenderTolerance,
|
getTolerance as getRenderTolerance,
|
||||||
@@ -142,6 +142,18 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
this.clipping = true;
|
this.clipping = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {CanvasRenderingContext2D}
|
||||||
|
*/
|
||||||
|
this.compositionContext_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.opacity_ = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -163,7 +175,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
const snapToPixel = !(
|
const snapToPixel = !(
|
||||||
viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]
|
viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]
|
||||||
);
|
);
|
||||||
const context = this.context;
|
const context = this.compositionContext_;
|
||||||
const width = Math.round(frameState.size[0] * pixelRatio);
|
const width = Math.round(frameState.size[0] * pixelRatio);
|
||||||
const height = Math.round(frameState.size[1] * pixelRatio);
|
const height = Math.round(frameState.size[1] * pixelRatio);
|
||||||
|
|
||||||
@@ -197,17 +209,44 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
} while (++world < endWorld);
|
} while (++world < endWorld);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupCompositionContext_() {
|
||||||
|
if (this.opacity_ !== 1) {
|
||||||
|
const compositionContext = createCanvasContext2D(
|
||||||
|
this.context.canvas.width,
|
||||||
|
this.context.canvas.height,
|
||||||
|
canvasPool
|
||||||
|
);
|
||||||
|
this.compositionContext_ = compositionContext;
|
||||||
|
} else {
|
||||||
|
this.compositionContext_ = this.context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseCompositionContext_() {
|
||||||
|
if (this.opacity_ !== 1) {
|
||||||
|
const alpha = this.context.globalAlpha;
|
||||||
|
this.context.globalAlpha = this.opacity_;
|
||||||
|
this.context.drawImage(this.compositionContext_.canvas, 0, 0);
|
||||||
|
this.context.globalAlpha = alpha;
|
||||||
|
releaseCanvas(this.compositionContext_);
|
||||||
|
canvasPool.push(this.compositionContext_.canvas);
|
||||||
|
this.compositionContext_ = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render declutter items for this layer
|
* Render declutter items for this layer
|
||||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||||
*/
|
*/
|
||||||
renderDeclutter(frameState) {
|
renderDeclutter(frameState) {
|
||||||
if (this.declutterExecutorGroup) {
|
if (this.declutterExecutorGroup) {
|
||||||
|
this.setupCompositionContext_();
|
||||||
this.renderWorlds(
|
this.renderWorlds(
|
||||||
this.declutterExecutorGroup,
|
this.declutterExecutorGroup,
|
||||||
frameState,
|
frameState,
|
||||||
frameState.declutterTree
|
frameState.declutterTree
|
||||||
);
|
);
|
||||||
|
this.releaseCompositionContext_();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,12 +266,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
|
|
||||||
const canvasTransform = transformToString(this.pixelTransform);
|
const canvasTransform = transformToString(this.pixelTransform);
|
||||||
|
|
||||||
this.useContainer(
|
this.useContainer(target, canvasTransform, this.getBackground(frameState));
|
||||||
target,
|
|
||||||
canvasTransform,
|
|
||||||
layerState.opacity,
|
|
||||||
this.getBackground(frameState)
|
|
||||||
);
|
|
||||||
const context = this.context;
|
const context = this.context;
|
||||||
const canvas = context.canvas;
|
const canvas = context.canvas;
|
||||||
|
|
||||||
@@ -263,6 +297,9 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
const viewState = frameState.viewState;
|
const viewState = frameState.viewState;
|
||||||
const projection = viewState.projection;
|
const projection = viewState.projection;
|
||||||
|
|
||||||
|
this.opacity_ = layerState.opacity;
|
||||||
|
this.setupCompositionContext_();
|
||||||
|
|
||||||
// clipped rendering if layer extent is set
|
// clipped rendering if layer extent is set
|
||||||
let clipped = false;
|
let clipped = false;
|
||||||
let render = true;
|
let render = true;
|
||||||
@@ -271,7 +308,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
render = intersectsExtent(layerExtent, frameState.extent);
|
render = intersectsExtent(layerExtent, frameState.extent);
|
||||||
clipped = render && !containsExtent(layerExtent, frameState.extent);
|
clipped = render && !containsExtent(layerExtent, frameState.extent);
|
||||||
if (clipped) {
|
if (clipped) {
|
||||||
this.clipUnrotated(context, frameState, layerExtent);
|
this.clipUnrotated(this.compositionContext_, frameState, layerExtent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,17 +317,13 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (clipped) {
|
if (clipped) {
|
||||||
context.restore();
|
this.compositionContext_.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.releaseCompositionContext_();
|
||||||
|
|
||||||
this.postRender(context, frameState);
|
this.postRender(context, frameState);
|
||||||
|
|
||||||
const opacity = cssOpacity(layerState.opacity);
|
|
||||||
const container = this.container;
|
|
||||||
if (opacity !== container.style.opacity) {
|
|
||||||
container.style.opacity = opacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.renderedRotation_ !== viewState.rotation) {
|
if (this.renderedRotation_ !== viewState.rotation) {
|
||||||
this.renderedRotation_ = viewState.rotation;
|
this.renderedRotation_ = viewState.rotation;
|
||||||
this.hitDetectionImageData_ = null;
|
this.hitDetectionImageData_ = null;
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ import {solveLinearSystem} from './math.js';
|
|||||||
|
|
||||||
let brokenDiagonalRendering_;
|
let brokenDiagonalRendering_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<HTMLCanvasElement>}
|
||||||
|
*/
|
||||||
|
export const canvasPool = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This draws a small triangle into a canvas by setting the triangle as the clip region
|
* This draws a small triangle into a canvas by setting the triangle as the clip region
|
||||||
* and then drawing a (too large) rectangle
|
* and then drawing a (too large) rectangle
|
||||||
@@ -217,7 +222,8 @@ export function render(
|
|||||||
) {
|
) {
|
||||||
const context = createCanvasContext2D(
|
const context = createCanvasContext2D(
|
||||||
Math.round(pixelRatio * width),
|
Math.round(pixelRatio * width),
|
||||||
Math.round(pixelRatio * height)
|
Math.round(pixelRatio * height),
|
||||||
|
canvasPool
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!opt_interpolate) {
|
if (!opt_interpolate) {
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ import TileState from '../TileState.js';
|
|||||||
import Triangulation from './Triangulation.js';
|
import Triangulation from './Triangulation.js';
|
||||||
import {
|
import {
|
||||||
calculateSourceExtentResolution,
|
calculateSourceExtentResolution,
|
||||||
|
canvasPool,
|
||||||
render as renderReprojected,
|
render as renderReprojected,
|
||||||
} from '../reproj.js';
|
} from '../reproj.js';
|
||||||
import {clamp} from '../math.js';
|
import {clamp} from '../math.js';
|
||||||
import {getArea, getIntersection} from '../extent.js';
|
import {getArea, getIntersection} from '../extent.js';
|
||||||
import {listen, unlistenByKey} from '../events.js';
|
import {listen, unlistenByKey} from '../events.js';
|
||||||
|
import {releaseCanvas} from '../dom.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {function(number, number, number, number) : import("../Tile.js").default} FunctionType
|
* @typedef {function(number, number, number, number) : import("../Tile.js").default} FunctionType
|
||||||
@@ -349,6 +351,18 @@ class ReprojTile extends Tile {
|
|||||||
this.sourcesListenerKeys_.forEach(unlistenByKey);
|
this.sourcesListenerKeys_.forEach(unlistenByKey);
|
||||||
this.sourcesListenerKeys_ = null;
|
this.sourcesListenerKeys_ = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove from the cache due to expiry
|
||||||
|
*/
|
||||||
|
release() {
|
||||||
|
if (this.canvas_) {
|
||||||
|
releaseCanvas(this.canvas_.getContext('2d'));
|
||||||
|
canvasPool.push(this.canvas_);
|
||||||
|
this.canvas_ = null;
|
||||||
|
}
|
||||||
|
super.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ReprojTile;
|
export default ReprojTile;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {assert} from '../asserts.js';
|
|||||||
import {ceil, clamp, floor} from '../math.js';
|
import {ceil, clamp, floor} from '../math.js';
|
||||||
import {createOrUpdate, getTopLeft} from '../extent.js';
|
import {createOrUpdate, getTopLeft} from '../extent.js';
|
||||||
import {createOrUpdate as createOrUpdateTileCoord} from '../tilecoord.js';
|
import {createOrUpdate as createOrUpdateTileCoord} from '../tilecoord.js';
|
||||||
|
import {intersectsLinearRing} from '../geom/flat/intersectsextent.js';
|
||||||
import {isSorted, linearFindNearest} from '../array.js';
|
import {isSorted, linearFindNearest} from '../array.js';
|
||||||
import {toSize} from '../size.js';
|
import {toSize} from '../size.js';
|
||||||
|
|
||||||
@@ -656,6 +657,22 @@ class TileGrid {
|
|||||||
return clamp(z, this.minZoom, this.maxZoom);
|
return clamp(z, this.minZoom, this.maxZoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tile with the provided tile coordinate intersects the given viewport.
|
||||||
|
* @param {import('../tilecoord.js').TileCoord} tileCoord Tile coordinate.
|
||||||
|
* @param {Array<number>} viewport Viewport as returned from {@link module:ol/extent.getRotatedViewport}.
|
||||||
|
* @return {boolean} The tile with the provided tile coordinate intersects the given viewport.
|
||||||
|
*/
|
||||||
|
tileCoordIntersectsViewport(tileCoord, viewport) {
|
||||||
|
return intersectsLinearRing(
|
||||||
|
viewport,
|
||||||
|
0,
|
||||||
|
viewport.length,
|
||||||
|
2,
|
||||||
|
this.getTileCoordExtent(tileCoord)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {!import("../extent.js").Extent} extent Extent for this tile grid.
|
* @param {!import("../extent.js").Extent} extent Extent for this tile grid.
|
||||||
* @private
|
* @private
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
import expect from '../expect.js';
|
import expect from '../expect.js';
|
||||||
import {cssOpacity, getFontParameters} from '../../../src/ol/css.js';
|
import {getFontParameters} from '../../../src/ol/css.js';
|
||||||
|
|
||||||
describe('ol.css', function () {
|
describe('ol.css', function () {
|
||||||
describe('cssOpacity()', function () {
|
|
||||||
it('converts number to string, 1 to ""', function () {
|
|
||||||
expect(cssOpacity(0.5)).to.eql('0.5');
|
|
||||||
expect(cssOpacity(1)).to.eql('');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('getFontParameters()', function () {
|
describe('getFontParameters()', function () {
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 109 KiB |