Skip to content

Commit

Permalink
feat(freehand): update stroke width to 2 and optimize freehand end po…
Browse files Browse the repository at this point in the history
…ints
  • Loading branch information
pubuzhixing8 committed Dec 8, 2024
1 parent 602fc3b commit b70773c
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class FreehandGenerator extends Generator<Freehand> {
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');
Expand Down
2 changes: 1 addition & 1 deletion packages/drawnix/src/plugins/freehand/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { PlaitCustomGeometry } from '@plait/draw';

export const DefaultFreehand = {
strokeColor: DEFAULT_COLOR,
strokeWidth: 4,
strokeWidth: 2,
};

export enum FreehandShape {
Expand Down
79 changes: 66 additions & 13 deletions packages/drawnix/src/plugins/freehand/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

0 comments on commit b70773c

Please sign in to comment.