Handle opacity on canvas instead of dom
This commit is contained in:
@@ -114,11 +114,3 @@ export const getFontParameters = function (fontSpec) {
|
||||
style.families = style.family.split(/,\s?/);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -195,12 +195,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
|
||||
|
||||
const canvasTransform = toTransformString(this.pixelTransform);
|
||||
|
||||
this.useContainer(
|
||||
target,
|
||||
canvasTransform,
|
||||
layerState.opacity,
|
||||
this.getBackground(frameState)
|
||||
);
|
||||
this.useContainer(target, canvasTransform, this.getBackground(frameState));
|
||||
|
||||
const context = this.context;
|
||||
const canvas = context.canvas;
|
||||
|
||||
@@ -20,6 +20,11 @@ import {
|
||||
import {createCanvasContext2D} from '../../dom.js';
|
||||
import {equals} from '../../array.js';
|
||||
|
||||
/**
|
||||
* @type {Array<HTMLCanvasElement>}
|
||||
*/
|
||||
export const canvasPool = [];
|
||||
|
||||
/**
|
||||
* @type {CanvasRenderingContext2D}
|
||||
*/
|
||||
@@ -143,17 +148,14 @@ class CanvasLayerRenderer extends LayerRenderer {
|
||||
* Get a rendering container from an existing target, if compatible.
|
||||
* @param {HTMLElement} target Potential render target.
|
||||
* @param {string} transform CSS Transform.
|
||||
* @param {number} opacity Opacity.
|
||||
* @param {string} [opt_backgroundColor] Background color.
|
||||
*/
|
||||
useContainer(target, transform, opacity, opt_backgroundColor) {
|
||||
useContainer(target, transform, opt_backgroundColor) {
|
||||
const layerClassName = this.getLayer().getClassName();
|
||||
let container, context;
|
||||
if (
|
||||
target &&
|
||||
target.className === layerClassName &&
|
||||
target.style.opacity === '' &&
|
||||
opacity === 1 &&
|
||||
(!opt_backgroundColor ||
|
||||
(target &&
|
||||
target.style.backgroundColor &&
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
getWidth,
|
||||
intersects,
|
||||
} from '../../extent.js';
|
||||
import {cssOpacity} from '../../css.js';
|
||||
import {fromUserExtent} from '../../proj.js';
|
||||
import {getUid} from '../../util.js';
|
||||
import {numberSafeCompareFunction} from '../../array.js';
|
||||
@@ -330,7 +329,12 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
||||
const uid = getUid(this);
|
||||
if (tile.getState() == TileState.LOADED) {
|
||||
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 (
|
||||
!this.newTiles_ &&
|
||||
(inTransition || this.renderedTiles.indexOf(tile) === -1)
|
||||
@@ -381,12 +385,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
||||
|
||||
const canvasTransform = toTransformString(this.pixelTransform);
|
||||
|
||||
this.useContainer(
|
||||
target,
|
||||
canvasTransform,
|
||||
layerState.opacity,
|
||||
this.getBackground(frameState)
|
||||
);
|
||||
this.useContainer(target, canvasTransform, this.getBackground(frameState));
|
||||
const context = this.context;
|
||||
const canvas = context.canvas;
|
||||
|
||||
@@ -572,11 +571,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
||||
if (canvasTransform !== canvas.style.transform) {
|
||||
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;
|
||||
}
|
||||
@@ -597,7 +591,10 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
||||
return;
|
||||
}
|
||||
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;
|
||||
if (alphaChanged) {
|
||||
this.context.save();
|
||||
@@ -618,7 +615,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
||||
if (alphaChanged) {
|
||||
this.context.restore();
|
||||
}
|
||||
if (alpha !== 1) {
|
||||
if (alpha !== layerState.opacity) {
|
||||
frameState.animate = true;
|
||||
} else if (transition) {
|
||||
tile.endTransition(uid);
|
||||
|
||||
@@ -105,8 +105,11 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
|
||||
!hints[ViewHint.INTERACTING] &&
|
||||
!isEmpty(renderedExtent)
|
||||
) {
|
||||
vectorRenderer.useContainer(null, null, 1);
|
||||
vectorRenderer.useContainer(null, null);
|
||||
const context = vectorRenderer.context;
|
||||
const layerState = frameState.layerStatesArray[frameState.layerIndex];
|
||||
context.globalAlpha = layerState.opacity;
|
||||
const imageLayerState = assign({}, layerState, {opacity: 1});
|
||||
const imageFrameState =
|
||||
/** @type {import("../../PluggableMap.js").FrameState} */ (
|
||||
assign({}, frameState, {
|
||||
@@ -118,6 +121,8 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
|
||||
rotation: 0,
|
||||
})
|
||||
),
|
||||
layerStatesArray: [imageLayerState],
|
||||
layerIndex: 0,
|
||||
})
|
||||
);
|
||||
let emptyImage = true;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @module ol/renderer/canvas/VectorLayer
|
||||
*/
|
||||
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 ViewHint from '../../ViewHint.js';
|
||||
import {
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
intersects as intersectsExtent,
|
||||
wrapX as wrapExtentX,
|
||||
} from '../../extent.js';
|
||||
import {cssOpacity} from '../../css.js';
|
||||
import {createCanvasContext2D, releaseCanvas} from '../../dom.js';
|
||||
import {
|
||||
defaultOrder as defaultRenderOrder,
|
||||
getTolerance as getRenderTolerance,
|
||||
@@ -142,6 +142,18 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
* @type {boolean}
|
||||
*/
|
||||
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 = !(
|
||||
viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]
|
||||
);
|
||||
const context = this.context;
|
||||
const context = this.compositionContext_;
|
||||
const width = Math.round(frameState.size[0] * pixelRatio);
|
||||
const height = Math.round(frameState.size[1] * pixelRatio);
|
||||
|
||||
@@ -197,17 +209,44 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
} 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
|
||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||
*/
|
||||
renderDeclutter(frameState) {
|
||||
if (this.declutterExecutorGroup) {
|
||||
this.setupCompositionContext_();
|
||||
this.renderWorlds(
|
||||
this.declutterExecutorGroup,
|
||||
frameState,
|
||||
frameState.declutterTree
|
||||
);
|
||||
this.releaseCompositionContext_();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,12 +266,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
|
||||
const canvasTransform = transformToString(this.pixelTransform);
|
||||
|
||||
this.useContainer(
|
||||
target,
|
||||
canvasTransform,
|
||||
layerState.opacity,
|
||||
this.getBackground(frameState)
|
||||
);
|
||||
this.useContainer(target, canvasTransform, this.getBackground(frameState));
|
||||
const context = this.context;
|
||||
const canvas = context.canvas;
|
||||
|
||||
@@ -263,6 +297,9 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
const viewState = frameState.viewState;
|
||||
const projection = viewState.projection;
|
||||
|
||||
this.opacity_ = layerState.opacity;
|
||||
this.setupCompositionContext_();
|
||||
|
||||
// clipped rendering if layer extent is set
|
||||
let clipped = false;
|
||||
let render = true;
|
||||
@@ -271,7 +308,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
render = intersectsExtent(layerExtent, frameState.extent);
|
||||
clipped = render && !containsExtent(layerExtent, frameState.extent);
|
||||
if (clipped) {
|
||||
this.clipUnrotated(context, frameState, layerExtent);
|
||||
this.clipUnrotated(this.compositionContext_, frameState, layerExtent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,17 +317,13 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
}
|
||||
|
||||
if (clipped) {
|
||||
context.restore();
|
||||
this.compositionContext_.restore();
|
||||
}
|
||||
|
||||
this.releaseCompositionContext_();
|
||||
|
||||
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) {
|
||||
this.renderedRotation_ = viewState.rotation;
|
||||
this.hitDetectionImageData_ = null;
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
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('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 () {
|
||||
const cases = [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user