diff --git a/packages/element/src/freedraw.ts b/packages/element/src/freedraw.ts new file mode 100644 index 0000000000..1f317304fe --- /dev/null +++ b/packages/element/src/freedraw.ts @@ -0,0 +1,91 @@ +import { + type LineSegment, + lineSegment, + type LocalPoint, + pointFrom, + pointFromVector, + vectorAntiNormal, + vectorFromPoint, + vectorNormal, + vectorNormalize, + vectorScale, +} from "@excalidraw/math"; + +import { type ExcalidrawFreeDrawElement } from "./types"; + +function generateSegments( + input: + | readonly [x: number, y: number, pressure: number][] + | readonly [x: number, y: number][], +): LineSegment[] { + if (input.length < 2) { + return []; + } + + const offset = ( + x: number, + y: number, + pressure: number, + direction: "left" | "right", + ) => { + const p = pointFrom(x, y); + const v = vectorNormalize(vectorFromPoint(p)); + const normal = direction === "left" ? vectorAntiNormal(v) : vectorNormal(v); + const scaled = vectorScale(normal, pressure / 2); + + return pointFromVector(scaled, p); + }; + + let idx = 0; + + const segments = Array(input.length * 2 - 1); + segments[idx++] = lineSegment( + offset(input[0][0], input[0][1], input[0][2] ?? 5, "left"), + offset(input[1][0], input[1][1], input[0][2] ?? 5, "left"), + ); + + for (let i = 2; i < input.length; i++) { + const point = input[i]; + const prev = segments[idx - 1][1]; + + segments[idx++] = lineSegment( + prev, + offset(point[0], point[1], point[2] ?? 5, "left"), + ); + } + + const prev = segments[idx - 1][1]; + segments[idx++] = lineSegment( + prev, + offset( + input[input.length - 1][0], + input[input.length - 1][1], + input[input.length - 1][2] ?? 5, + "right", + ), + ); + + for (let i = input.length - 2; i >= 0; i--) { + const point = input[i]; + const prev = segments[idx - 1][1]; + + segments[idx++] = lineSegment( + prev, + offset(point[0], point[1], point[2] ?? 5, "right"), + ); + } + + return segments; +} + +export function getStroke( + input: + | readonly [x: number, y: number, pressure: number][] + | readonly [x: number, y: number][], + options: any, + element: ExcalidrawFreeDrawElement, +): LocalPoint[] { + const segments = generateSegments(input); + + return [segments[0][0], ...segments.map((s) => s[1])]; +} diff --git a/packages/element/src/renderElement.ts b/packages/element/src/renderElement.ts index 6a49d4202f..5c8c89c23a 100644 --- a/packages/element/src/renderElement.ts +++ b/packages/element/src/renderElement.ts @@ -1,5 +1,5 @@ import rough from "roughjs/bin/rough"; -import { getStroke } from "perfect-freehand"; +//import { getStroke } from "perfect-freehand"; import { type GlobalPoint, @@ -80,6 +80,7 @@ import type { import type { StrokeOptions } from "perfect-freehand"; import type { RoughCanvas } from "roughjs/bin/canvas"; +import { getStroke } from "./freedraw"; // using a stronger invert (100% vs our regular 93%) and saturate // as a temp hack to make images in dark theme look closer to original @@ -1102,10 +1103,10 @@ export function getFreedrawOutlineAsSegments( export function getFreedrawOutlinePoints(element: ExcalidrawFreeDrawElement) { // If input points are empty (should they ever be?) return a dot const inputPoints = element.simulatePressure - ? element.points - : element.points.length - ? element.points.map(([x, y], i) => [x, y, element.pressures[i]]) - : [[0, 0, 0.5]]; + ? (element.points as readonly [number, number][]) + : ((element.points.length + ? element.points.map(([x, y], i) => [x, y, element.pressures[i]]) + : [[0, 0, 0.5]]) as [number, number, number][]); // Consider changing the options for simulated pressure vs real pressure const options: StrokeOptions = { @@ -1118,7 +1119,7 @@ export function getFreedrawOutlinePoints(element: ExcalidrawFreeDrawElement) { last: true, }; - return getStroke(inputPoints as number[][], options) as [number, number][]; + return getStroke(inputPoints, options, element) as [number, number][]; } function med(A: number[], B: number[]) { diff --git a/packages/math/src/vector.ts b/packages/math/src/vector.ts index c520fce244..31bb564c44 100644 --- a/packages/math/src/vector.ts +++ b/packages/math/src/vector.ts @@ -158,3 +158,8 @@ export const vectorNormalize = (v: Vector): Vector => { * Calculate the right-hand normal of the vector. */ export const vectorNormal = (v: Vector): Vector => vector(v[1], -v[0]); + +/** + * Calculate the left-hand normal of the vector. + */ +export const vectorAntiNormal = (v: Vector): Vector => vector(-v[1], v[0]);