diff --git a/packages/drawnix/src/plugins/freehand/freehand.generator.ts b/packages/drawnix/src/plugins/freehand/freehand.generator.ts index 6ddc3e4..7442333 100644 --- a/packages/drawnix/src/plugins/freehand/freehand.generator.ts +++ b/packages/drawnix/src/plugins/freehand/freehand.generator.ts @@ -8,7 +8,7 @@ export class FreehandGenerator extends Generator { protected draw(element: Freehand): SVGGElement | undefined { const option: Options = { ...DefaultFreehand }; const g = PlaitBoard.getRoughSVG(this.board).curve( - gaussianSmooth(element.points, 1, 9), + gaussianSmooth(element.points, 1, 3), option ); setStrokeLinecap(g, 'round'); diff --git a/packages/drawnix/src/plugins/freehand/type.ts b/packages/drawnix/src/plugins/freehand/type.ts index 50c3d74..60a43f7 100644 --- a/packages/drawnix/src/plugins/freehand/type.ts +++ b/packages/drawnix/src/plugins/freehand/type.ts @@ -3,7 +3,7 @@ import { PlaitCustomGeometry } from '@plait/draw'; export const DefaultFreehand = { strokeColor: DEFAULT_COLOR, - strokeWidth: 4, + strokeWidth: 2, }; export enum FreehandShape { diff --git a/packages/drawnix/src/plugins/freehand/utils.ts b/packages/drawnix/src/plugins/freehand/utils.ts index 8ec07fd..9c32806 100644 --- a/packages/drawnix/src/plugins/freehand/utils.ts +++ b/packages/drawnix/src/plugins/freehand/utils.ts @@ -74,32 +74,85 @@ export function gaussianWeight(x: number, sigma: number) { return Math.exp(-(x * x) / (2 * sigma * sigma)); } -export function gaussianSmooth(points: Point[], sigma: number, windowSize: number) { +export function gaussianSmooth( + points: Point[], + sigma: number, + windowSize: number +) { if (points.length < 2) return points; const halfWindow = Math.floor(windowSize / 2); const smoothedPoints: Point[] = []; - + + // 方法1:端点镜像 + function getMirroredPoint(idx: number): Point { + if (idx < 0) { + // 左端镜像 + const mirrorIdx = -idx - 1; + if (mirrorIdx < points.length) { + // 以第一个点为中心的对称点 + return [ + 2 * points[0][0] - points[mirrorIdx][0], + 2 * points[0][1] - points[mirrorIdx][1], + ]; + } + } else if (idx >= points.length) { + // 右端镜像 + const mirrorIdx = 2 * points.length - idx - 1; + if (mirrorIdx >= 0) { + // 以最后一个点为中心的对称点 + return [ + 2 * points[points.length - 1][0] - points[mirrorIdx][0], + 2 * points[points.length - 1][1] - points[mirrorIdx][1], + ]; + } + } + return points[idx]; + } + + // 方法2:自适应窗口 + function getAdaptiveWindow(i: number): number { + // 端点处使用较小的窗口 + const distToEdge = Math.min(i, points.length - 1 - i); + return Math.min(halfWindow, distToEdge + Math.floor(halfWindow / 2)); + } + for (let i = 0; i < points.length; i++) { let sumX = 0; let sumY = 0; let weightSum = 0; - for (let j = -halfWindow; j <= halfWindow; j++) { + // 对端点使用自适应窗口 + const adaptiveWindow = getAdaptiveWindow(i); + + for (let j = -adaptiveWindow; j <= adaptiveWindow; j++) { const idx = i + j; - if (idx >= 0 && idx < points.length) { - const weight = gaussianWeight(j, sigma); - sumX += points[idx][0] * weight; - sumY += points[idx][1] * weight; - weightSum += weight; + const point = getMirroredPoint(idx); + + // 端点处使用渐变权重 + let weight = gaussianWeight(j, sigma); + + // 端点权重调整 + if (i < halfWindow || i >= points.length - halfWindow) { + // 增加端点原始值的权重 + const edgeFactor = 1 + 0.5 * (1 - Math.abs(j) / adaptiveWindow); + weight *= j === 0 ? edgeFactor : 1; } + + sumX += point[0] * weight; + sumY += point[1] * weight; + weightSum += weight; } - smoothedPoints.push([ - sumX / weightSum, - sumY / weightSum - ]); + // 端点处的特殊处理 + if (i === 0 || i === points.length - 1) { + // 保持端点不变 + smoothedPoints.push([points[i][0], points[i][1]]); + } else { + // 平滑中间点 + smoothedPoints.push([sumX / weightSum, sumY / weightSum]); + } } return smoothedPoints; -} \ No newline at end of file +}