diff --git a/common/changes/@visactor/vrender-core/feat-drawMode_2025-02-20-13-42.json b/common/changes/@visactor/vrender-core/feat-drawMode_2025-02-20-13-42.json new file mode 100644 index 000000000..5246b724a --- /dev/null +++ b/common/changes/@visactor/vrender-core/feat-drawMode_2025-02-20-13-42.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-core", + "comment": "feat: group support drawMode attribute", + "type": "none" + } + ], + "packageName": "@visactor/vrender-core" +} diff --git a/packages/vrender-core/src/canvas/contributions/base-canvas.ts b/packages/vrender-core/src/canvas/contributions/base-canvas.ts index 7cd30cedd..bfdcc4e64 100644 --- a/packages/vrender-core/src/canvas/contributions/base-canvas.ts +++ b/packages/vrender-core/src/canvas/contributions/base-canvas.ts @@ -55,6 +55,9 @@ export abstract class BaseCanvas implements ICanvas { get nativeCanvas(): HTMLCanvasElement { return this._nativeCanvas; } + set nativeCanvas(nativeCanvas: HTMLCanvasElement) { + this._nativeCanvas = nativeCanvas; + } get width(): number { return this._pixelWidth; diff --git a/packages/vrender-core/src/graphic/config.ts b/packages/vrender-core/src/graphic/config.ts index 942f22f64..c20613892 100644 --- a/packages/vrender-core/src/graphic/config.ts +++ b/packages/vrender-core/src/graphic/config.ts @@ -266,7 +266,9 @@ export const DefaultGroupAttribute: Required = { alignContent: 'flex-start', baseOpacity: 1, cornerType: 'round' -}; + // 默认是0,不需要主题 + // drawMode: 0 +} as any; export const DefaultGlyphAttribute: Required = { ...DefaultAttribute, diff --git a/packages/vrender-core/src/interface/graphic/group.ts b/packages/vrender-core/src/interface/graphic/group.ts index 549db55b6..0ba75c140 100644 --- a/packages/vrender-core/src/interface/graphic/group.ts +++ b/packages/vrender-core/src/interface/graphic/group.ts @@ -62,6 +62,13 @@ export type IGroupAttribute = { * 基准的透明度,用于控制group下面整体图元的透明度 */ baseOpacity?: number; + /** + * 绘制模式 + * 0 - 直接绘制 + * 1 - 绘制到新Canvas上,再绘制回来,需要绘制背景,然后绘制group + * 2 - 绘制到新Canvas上,再绘制回来,不需要绘制背景,只需要绘制group + */ + drawMode?: 0 | 1 | 2; }; export type IGroupGraphicAttribute = Partial & Partial; diff --git a/packages/vrender-core/src/render/contributions/render/group-render.ts b/packages/vrender-core/src/render/contributions/render/group-render.ts index e6a2e068d..3130ada96 100644 --- a/packages/vrender-core/src/render/contributions/render/group-render.ts +++ b/packages/vrender-core/src/render/contributions/render/group-render.ts @@ -25,6 +25,7 @@ import { GROUP_NUMBER_TYPE } from '../../../graphic/constants'; import { BaseRenderContributionTime } from '../../../common/enums'; import { defaultGroupBackgroundRenderContribution } from './contributions'; import { multiplyMat4Mat4 } from '../../../common/matrix'; +import { vglobal } from '../../../modules'; @injectable() export class DefaultCanvasGroupRender implements IGraphicRender { @@ -220,8 +221,47 @@ export class DefaultCanvasGroupRender implements IGraphicRender { if (!context) { return; } + // debugger; - const { clip, baseOpacity = 1 } = group.attribute; + const { clip, baseOpacity = 1, drawMode, x, y, width, height } = group.attribute; + const lastNativeContext = context.nativeContext; + const lastNativeCanvas = context.canvas.nativeCanvas; + + if (drawMode > 0) { + // 绘制到新的Canvas上,然后再绘制回来 + const canvas = context.canvas; + const newCanvas = vglobal.createCanvas({ width: canvas.width, height: canvas.height, dpr: 1 }); + const newContext = newCanvas.getContext('2d'); + const transform = context.nativeContext.getTransform(); + // 首先应用transform + newContext.setTransform(transform.a, transform.b, transform.c, transform.d, transform.e, transform.f); + // 然后将背景绘制到新的canvas上,只绘制group的Bounds区域 + // 如果drawMode === 1,则需要背景 + // 如果drawMode === 2,则不需要背景,只需要group即可 + if (drawMode === 1) { + newContext.save(); + newContext.clearRect(0, 0, canvas.width, canvas.height); + newContext.beginPath(); + + newContext.rect(x, y, width, height); + newContext.clip(); + newContext.drawImage( + canvas.nativeCanvas, + 0, + 0, + canvas.width, + canvas.height, + 0, + 0, + canvas.displayWidth, + canvas.displayHeight + ); + newContext.restore(); + } + // 狸猫换太子,把新的context赋值给context + context.nativeContext = newContext; + canvas.nativeCanvas = newCanvas; + } if (clip) { context.save(); } else { @@ -304,6 +344,34 @@ export class DefaultCanvasGroupRender implements IGraphicRender { context.baseGlobalAlpha = baseGlobalAlpha; + if (drawMode > 0) { + // 将原始的context和canvas恢复,另外将newCanvas上的内容绘制到lastCanvas上 + const newContext = context.nativeContext; + const newCanvas = context.canvas.nativeCanvas; + lastNativeContext.save(); + lastNativeContext.setTransform(context.dpr, 0, 0, context.dpr, 0, 0, true); + // 如果drawMode === 1,则需要清一下之前的背景,否则背景就重复绘制了一次 + if (drawMode === 1) { + newContext.rect(x, y, width, height); + } + lastNativeContext.drawImage( + newCanvas, + 0, + 0, + newCanvas.width, + newCanvas.height, + 0, + 0, + context.canvas.displayWidth, + context.canvas.displayHeight + ); + const transform = newContext.getTransform(); + lastNativeContext.setTransform(transform.a, transform.b, transform.c, transform.d, transform.e, transform.f); + context.nativeContext = lastNativeContext; + context.canvas.nativeCanvas = lastNativeCanvas; + lastNativeContext.restore(); + } + if (p && p.then) { p.then(() => { if (clip) { diff --git a/packages/vrender-kits/src/canvas/contributions/browser/context.ts b/packages/vrender-kits/src/canvas/contributions/browser/context.ts index 5db1d5ceb..942319f9c 100644 --- a/packages/vrender-kits/src/canvas/contributions/browser/context.ts +++ b/packages/vrender-kits/src/canvas/contributions/browser/context.ts @@ -1075,15 +1075,11 @@ export class BrowserContext2d implements IContext2d { if (blur) { _context.filter = `blur(${blur}px)`; this._clearFilterStyle = true; - } else if (this._clearFilterStyle) { - _context.filter = 'blur(0px)'; - this._clearFilterStyle = false; - } - if (filter) { + } else if (filter) { _context.filter = filter; this._clearFilterStyle = true; } else if (this._clearFilterStyle) { - _context.filter = ''; + _context.filter = 'blur(0px)'; this._clearFilterStyle = false; } diff --git a/packages/vrender/__tests__/browser/src/pages/image-cloud.ts b/packages/vrender/__tests__/browser/src/pages/image-cloud.ts new file mode 100644 index 000000000..ba7d4d01c --- /dev/null +++ b/packages/vrender/__tests__/browser/src/pages/image-cloud.ts @@ -0,0 +1,68 @@ +import { createStage, createImage, createGroup } from '@visactor/vrender'; +import { addShapesToStage, colorPools } from '../utils'; + +const urlPng = 'https://vega.github.io/images/idl-logo.png'; +const svg = + ''; +const svg1 = + ''; +const base64 = + 'data:image/png;base64,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'; +// const urlSvg = 'https://replace-with-svg-link.svg'; + +const dogImage = 'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/vrender/lovely_dog.jpg'; +const visactorIcon = 'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/vrender/vsactor-icon.png'; + +export const page = () => { + const shapes = []; + + shapes.push( + createImage({ + x: 0, + y: 0, + image: dogImage, + height: 600 + }) + ); + + const group = createGroup({ + x: 200, + y: 200, + width: 300, + height: 300, + background: visactorIcon, + drawMode: 2 + }); + + for (let i = 0; i < 300; i++) { + const image = createImage({ + x: Math.random() * 350 - 25, + y: Math.random() * 350 - 25, + width: 100, + image: dogImage, + globalCompositeOperation: 'source-atop' + }); + group.add(image); + } + shapes.push(group); + + shapes.forEach(g => { + g.addEventListener('click', () => { + console.log('click', g._uid); + }); + }); + + const stage = createStage({ + canvas: 'main', + width: 1200, + height: 600, + viewWidth: 1200, + viewHeight: 600 + }); + + stage.render(); + + shapes.forEach(shape => { + stage.defaultLayer.add(shape); + }); +}; diff --git a/packages/vrender/__tests__/browser/src/pages/index.ts b/packages/vrender/__tests__/browser/src/pages/index.ts index fe7c27976..882c824cf 100644 --- a/packages/vrender/__tests__/browser/src/pages/index.ts +++ b/packages/vrender/__tests__/browser/src/pages/index.ts @@ -35,6 +35,10 @@ export const pages = [ name: '手势事件测试', path: 'gesture-test' }, + { + name: 'image-cloud', + path: 'image-cloud' + }, { name: 'arc绘制', path: 'arc'