UNPKG

@meta2d/core

Version:

@meta2d/core: Powerful, Beautiful, Simple, Open - Web-Based 2D At Its Best .

368 lines 14.9 kB
import { ctxFlip, ctxRotate, drawImage, setGlobalAlpha, renderPen, CanvasLayer, } from '../pen'; import { createOffscreen } from './offscreen'; export class CanvasTemplate { parentElement; store; canvas = document.createElement('canvas'); offscreen = createOffscreen(); bgOffscreen = createOffscreen(); patchFlags; bgPatchFlags; fit; //是否自适应布局 constructor(parentElement, store) { this.parentElement = parentElement; this.store = store; parentElement.appendChild(this.canvas); this.canvas.style.backgroundRepeat = 'no-repeat'; this.canvas.style.backgroundSize = '100% 100%'; this.canvas.style.position = 'absolute'; this.canvas.style.top = '0'; this.canvas.style.left = '0'; } resize(w, h) { this.canvas.style.width = w + 'px'; this.canvas.style.height = h + 'px'; w = (w * this.store.dpiRatio) | 0; h = (h * this.store.dpiRatio) | 0; this.canvas.width = w; this.canvas.height = h; this.bgOffscreen.width = w; this.bgOffscreen.height = h; this.offscreen.width = w; this.offscreen.height = h; this.bgOffscreen .getContext('2d') .scale(this.store.dpiRatio, this.store.dpiRatio); this.bgOffscreen.getContext('2d').textBaseline = 'middle'; this.offscreen .getContext('2d') .scale(this.store.dpiRatio, this.store.dpiRatio); this.offscreen.getContext('2d').textBaseline = 'middle'; this.init(); } init() { this.bgOffscreen .getContext('2d') .clearRect(0, 0, this.canvas.width, this.canvas.height); this.offscreen .getContext('2d') .clearRect(0, 0, this.canvas.width, this.canvas.height); this.patchFlags = true; this.bgPatchFlags = true; // for (const pen of this.store.data.pens) { // if (this.hasImage(pen)) { // // 只影响本层的 // pen.calculative.imageDrawed = false; // } // } // this.store.patchFlagsBackground = true; // this.store.patchFlagsTop = true; } hidden() { this.canvas.style.display = 'none'; } show() { this.canvas.style.display = 'block'; } clear() { this.bgOffscreen .getContext('2d') .clearRect(0, 0, this.canvas.width, this.canvas.height); this.offscreen .getContext('2d') .clearRect(0, 0, this.canvas.width, this.canvas.height); this.canvas .getContext('2d') .clearRect(0, 0, this.canvas.width, this.canvas.height); this.bgPatchFlags = true; this.patchFlags = true; } render() { if (this.bgPatchFlags) { const ctx = this.bgOffscreen.getContext('2d'); ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); const width = this.store.data.width || this.store.options.width; const height = this.store.data.height || this.store.options.height; const x = this.store.data.x || this.store.options.x || 0; const y = this.store.data.y || this.store.options.y || 0; const background = this.store.data.background || this.store.styles.background; // this.store.data.background || this.store.options.background; if (background) { ctx.save(); ctx.fillStyle = background; ctx.globalAlpha = this.store.data.globalAlpha ?? this.store.options.globalAlpha; if (width && height && !this.fit) { ctx.shadowOffsetX = this.store.options.shadowOffsetX; ctx.shadowOffsetY = this.store.options.shadowOffsetY; ctx.shadowBlur = this.store.options.shadowBlur; ctx.shadowColor = this.store.options.shadowColor; ctx.fillRect(this.store.data.origin.x + x, this.store.data.origin.y + y, width * this.store.data.scale, height * this.store.data.scale); } else { ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); } ctx.restore(); } if (width && height && this.store.bkImg) { ctx.save(); if (this.fit) { ctx.drawImage(this.store.bkImg, 0, 0, this.canvas.offsetWidth, this.canvas.offsetHeight); } else { ctx.drawImage(this.store.bkImg, this.store.data.origin.x + x, this.store.data.origin.y + y, width * this.store.data.scale, height * this.store.data.scale); } ctx.restore(); } this.renderGrid(ctx); } if (this.patchFlags) { const ctx = this.offscreen.getContext('2d'); ctx.save(); ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); ctx.translate(this.store.data.x, this.store.data.y); for (const pen of this.store.data.pens) { if (!isFinite(pen.x)) { continue; } if ( // pen.template pen.canvasLayer === CanvasLayer.CanvasTemplate && pen.calculative.inView) { // if (pen.name === 'combine' && !pen.draw){ // continue; // } //非图片 renderPen(ctx, pen); //图片 if (pen.image && pen.name !== 'gif' && pen.calculative.img) { ctx.save(); ctxFlip(ctx, pen); if (pen.rotateByRoot || pen.calculative.rotate) { ctxRotate(ctx, pen); } setGlobalAlpha(ctx, pen); drawImage(ctx, pen); ctx.restore(); } } } ctx.restore(); } if (this.patchFlags || this.bgPatchFlags) { const ctxCanvas = this.canvas.getContext('2d'); ctxCanvas.clearRect(0, 0, this.canvas.width, this.canvas.height); ctxCanvas.drawImage(this.bgOffscreen, 0, 0, this.canvas.width, this.canvas.height); ctxCanvas.drawImage(this.offscreen, 0, 0, this.canvas.width, this.canvas.height); this.patchFlags = false; this.bgPatchFlags = false; } } renderGrid(ctx) { const { data, options } = this.store; const { grid, gridRotate, gridColor, gridSize, scale, origin } = data; if (!(grid ?? options.grid)) { // grid false 时不绘制, undefined 时看 options.grid return; } ctx.save(); const width = (data.width || options.width) * scale; const height = (data.height || options.height) * scale; const startX = (data.x || options.x || 0) + origin.x; const startY = (data.y || options.y || 0) + origin.y; // if (width && height && gridRotate) { // ctx.translate(width / 2, height / 2); // ctx.rotate((gridRotate * Math.PI) / 180); // ctx.translate(-width / 2, -height / 2); // } ctx.lineWidth = 1; ctx.strokeStyle = gridColor || options.gridColor; ctx.beginPath(); let size = (gridSize || options.gridSize) * scale; size = size < 0 ? 0 : size; if (!width || !height) { const ratio = this.store.dpiRatio; const cW = this.canvas.width / ratio; const cH = this.canvas.height / ratio; const m = startX / size; const n = startY / size; const offset = size * 10; //补偿值 const newX = startX - Math.ceil(m) * size; const newY = startY - Math.ceil(n) * size; const endX = cW + newX + offset; const endY = cH + newY + offset; if (gridRotate) { // 菱形效果 // drawParallelLines(ctx, cW, cH, size, gridRotate); // drawParallelLines(ctx, cW, cH, size, -gridRotate); // 计算两组斜线的方向向量 const radian1 = (gridRotate * Math.PI) / 180; const radian2 = radian1 + Math.PI / 2; // 计算法向量用于间距控制 const normal1 = { x: Math.sin(radian1), y: -Math.cos(radian1) }; const normal2 = { x: Math.sin(radian2), y: -Math.cos(radian2) }; drawPreciseLines(ctx, cW, cH, size, normal1, radian1); drawPreciseLines(ctx, cW, cH, size, normal2, radian2); } else { for (let i = newX; i <= endX; i += size) { ctx.moveTo(i, newY); ctx.lineTo(i, cH + newY + offset); } for (let i = newY; i <= endY; i += size) { ctx.moveTo(newX, i); ctx.lineTo(cW + newX + offset, i); } } } else { if (gridRotate) { const radian1 = gridRotate * Math.PI / 180; const radian2 = radian1 + Math.PI / 2; // 垂直角度 // 第一组斜线的法向量(用于间距控制) const normal1 = { x: Math.sin(radian1), y: -Math.cos(radian1) }; // 第二组斜线的法向量 const normal2 = { x: Math.sin(radian2), y: -Math.cos(radian2) }; // 绘制第一组斜线 drawPreciseLinesInRect(ctx, startX, startY, width, height, size, normal1, radian1); // 绘制第二组垂直斜线 drawPreciseLinesInRect(ctx, startX, startY, width, height, size, normal2, radian1); } else { const endX = width + startX; const endY = height + startY; for (let i = startX; i <= endX; i += size) { ctx.moveTo(i, startY); ctx.lineTo(i, height + startY); } for (let i = startY; i <= endY; i += size) { ctx.moveTo(startX, i); ctx.lineTo(width + startX, i); } } } ctx.stroke(); ctx.restore(); } } function drawParallelLines(ctx, width, height, spacing, angle) { const radian = (angle * Math.PI) / 180; const cos = Math.cos(radian); const sin = Math.sin(radian); const lineCount = Math.ceil(Math.max(width, height) / (spacing * Math.min(Math.abs(cos), Math.abs(sin)))) * 2; ctx.beginPath(); for (let i = -lineCount; i < lineCount; i++) { const x = i * spacing; if (sin > 0) { ctx.moveTo(x, 0); ctx.lineTo(x + (height / sin) * cos, height); } else { ctx.moveTo(x, height); ctx.lineTo(x - (height / sin) * cos, 0); } } ctx.stroke(); } function drawPreciseLines(ctx, width, height, spacing, normal, angle) { // 计算边界点 const corners = [ { x: 0, y: 0 }, { x: width, y: 0 }, { x: width, y: height }, { x: 0, y: height }, ]; // 计算投影范围 let minProjection = Infinity; let maxProjection = -Infinity; corners.forEach((corner) => { const proj = corner.x * normal.x + corner.y * normal.y; minProjection = Math.min(minProjection, proj); maxProjection = Math.max(maxProjection, proj); }); // 计算线条数量 const lineCount = Math.ceil((maxProjection - minProjection) / spacing); ctx.beginPath(); for (let i = 0; i <= lineCount; i++) { const d = minProjection + i * spacing; // 计算与边界的交点 let points = []; for (let j = 0; j < corners.length; j++) { const p1 = corners[j]; const p2 = corners[(j + 1) % corners.length]; const denom = normal.x * (p2.y - p1.y) - normal.y * (p2.x - p1.x); if (Math.abs(denom) > 1e-6) { const t = (d - p1.x * normal.x - p1.y * normal.y) / (normal.x * (p2.x - p1.x) + normal.y * (p2.y - p1.y)); if (t >= 0 && t <= 1) { const x = p1.x + t * (p2.x - p1.x); const y = p1.y + t * (p2.y - p1.y); points.push({ x, y }); } } } // 绘制线条(确保有2个交点) if (points.length >= 2) { ctx.moveTo(points[0].x, points[0].y); ctx.lineTo(points[1].x, points[1].y); } } ctx.stroke(); } function drawPreciseLinesInRect(ctx, x, y, width, height, spacing, normal, angle) { const corners = [ { x, y }, // 左上 { x: x + width, y }, // 右上 { x: x + width, y: y + height }, // 右下 { x, y: y + height } // 左下 ]; // 1. 计算法向量在四个角上的投影范围 let min = Infinity, max = -Infinity; corners.forEach(p => { const proj = p.x * normal.x + p.y * normal.y; min = Math.min(min, proj); max = Math.max(max, proj); }); // 2. 计算需要绘制的线条数量和起始位置 const totalLength = max - min; const lineCount = Math.ceil(totalLength / spacing); const startOffset = min; // 3. 绘制每条斜线 ctx.beginPath(); for (let i = 0; i <= lineCount; i++) { const d = startOffset + i * spacing; // 计算与矩形边的交点 const points = []; for (let j = 0; j < corners.length; j++) { const p1 = corners[j]; const p2 = corners[(j + 1) % 4]; // 线段与斜线的交点计算 const edgeVecX = p2.x - p1.x; const edgeVecY = p2.y - p1.y; const denominator = normal.x * edgeVecY - normal.y * edgeVecX; if (Math.abs(denominator) > 1e-6) { const t = (d - p1.x * normal.x - p1.y * normal.y) / (normal.x * edgeVecX + normal.y * edgeVecY); if (t >= 0 && t <= 1) { points.push({ x: p1.x + t * edgeVecX, y: p1.y + t * edgeVecY }); } } } // 连接交点(确保有2个交点) if (points.length >= 2) { ctx.moveTo(points[0].x, points[0].y); ctx.lineTo(points[1].x, points[1].y); } } ctx.stroke(); } //# sourceMappingURL=canvasTemplate.js.map