/* eslint-disable @typescript-eslint/ban-types */
import Canvas2D from '../../core/Canvas';
import { TileRenderingContext } from '../types';
import LayerAbstractRenderer from './LayerAbstractRenderer';
/**
* 在 HTMLCanvasElement 上渲染图层的基类
* @english
* Base Class to render layer on HTMLCanvasElement
* @abstract
* @protected
* @memberOf renderer
* @extends Class
*/
class CanvasRenderer extends LayerAbstractRenderer {
gl: TileRenderingContext;
/**
* Ask whether the layer renderer needs to redraw
*/
needToRedraw(): boolean {
const map = this.getMap();
if (map.isInteracting() || map.getRenderer().isViewChanged()) {
// don't redraw when map is moving without any pitch
return !(!map.getPitch() && map.isMoving() && !map.isZooming() && !map.isRotating() && !this.layer.options['forceRenderOnMoving']);
}
return false;
}
createContext(): void {
//Be compatible with layer renderers that overrides create canvas and create gl/context
if (this.gl && this.gl.canvas === this.canvas || this.context) {
return;
}
//disable willReadFrequently for render performance.Performance improved by 200 times
this.context = Canvas2D.getCanvas2DPerformanceContext(this.canvas);
if (!this.context) {
return;
}
this.context.dpr = 1;
if (this.layer.options['globalCompositeOperation']) {
this.context.globalCompositeOperation = this.layer.options['globalCompositeOperation'];
}
const dpr = this.getMap().getDevicePixelRatio();
if (dpr !== 1) {
this._canvasContextScale(this.context, dpr);
}
}
resetCanvasTransform(): void {
if (!this.context) {
return;
}
const dpr = this.getMap().getDevicePixelRatio();
this.context.setTransform(dpr, 0, 0, dpr, 0, 0);
}
/**
* Clear the canvas to blank
*/
clearCanvas(): void {
if (!this.context) {
return;
}
const map = this.getMap();
if (!map) {
return;
}
//fix #1597
const r = this.mapDPR || map.getDevicePixelRatio();
const rScale = 1 / r;
const w = this.canvas.width * rScale, h = this.canvas.height * rScale;
Canvas2D.clearRect(this.context, 0, 0, Math.max(w, this.canvas.width), Math.max(h, this.canvas.height));
}
clear() {
this.clearCanvas();
this.setToRedraw();
}
/**
* @english
* Prepare the canvas for rendering.
* 1. Clear the canvas to blank.
* 2. Clip the canvas by mask if there is any and return the mask's extent
* @return {PointExtent} mask's extent of current zoom's 2d point.
*/
prepareCanvas(): any {
if (!this.canvas) {
this.createCanvas();
this.createContext();
this.layer.onCanvasCreate();
/**
* canvascreate event, fired when canvas created.
*
* @event Layer#canvascreate
* @type {Object}
* @property {String} type - canvascreate
* @property {Layer} target - layer
* @property {CanvasRenderingContext2D} context - canvas's context
* @property {WebGLRenderingContext2D} gl - canvas's webgl context
*/
this.layer.fire('canvascreate', {
'context': this.context,
'gl': this.gl
});
} else {
this.resetCanvasTransform();
this.clearCanvas();
this.resizeCanvas();
}
delete this._maskExtent;
const mask = this.layer.getMask();
// this.context may be not available
if (!mask) {
this.layer.fire('renderstart', {
'context': this.context,
'gl': this.gl
});
return null;
}
const maskExtent2D = this._maskExtent = mask._getMaskPainter().get2DExtent();
//fix vt _extent2D is null
if (maskExtent2D && this._extent2D && !maskExtent2D.intersects(this._extent2D)) {
this.layer.fire('renderstart', {
'context': this.context,
'gl': this.gl
});
return maskExtent2D;
}
/**
* renderstart event, fired when layer starts to render.
*
* @event Layer#renderstart
* @type {Object}
* @property {String} type - renderstart
* @property {Layer} target - layer
* @property {CanvasRenderingContext2D} context - canvas's context
*/
this.layer.fire('renderstart', {
'context': this.context,
'gl': this.gl
});
return maskExtent2D;
}
/**
* onResize
* @param {Object} param event parameters
*/
onResize(_param: any) {
delete this._extent2D;
this.resizeCanvas();
this.setToRedraw();
}
}
export default CanvasRenderer;