@stroke-stabilizer/core 0.2.16 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -2
- package/dist/StabilizedPointer.d.ts +1 -1
- package/dist/filters/DouglasPeuckerFilter.d.ts +37 -0
- package/dist/filters/DouglasPeuckerFilter.d.ts.map +1 -0
- package/dist/filters/EmaFilter.d.ts.map +1 -1
- package/dist/filters/KalmanFilter.d.ts.map +1 -1
- package/dist/filters/LinearPredictionFilter.d.ts.map +1 -1
- package/dist/filters/MovingAverageFilter.d.ts.map +1 -1
- package/dist/filters/OneEuroFilter.d.ts.map +1 -1
- package/dist/filters/StringFilter.d.ts.map +1 -1
- package/dist/filters/index.d.ts +2 -0
- package/dist/filters/index.d.ts.map +1 -1
- package/dist/index.cjs +578 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +578 -14
- package/dist/index.js.map +1 -1
- package/dist/prediction.d.ts +102 -0
- package/dist/prediction.d.ts.map +1 -0
- package/dist/spline.d.ts +53 -0
- package/dist/spline.d.ts.map +1 -0
- package/dist/svg.d.ts +79 -0
- package/dist/svg.d.ts.map +1 -0
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/kernels/types.ts","../src/smooth.ts","../src/StabilizedPointer.ts","../src/filters/NoiseFilter.ts","../src/filters/KalmanFilter.ts","../src/filters/MovingAverageFilter.ts","../src/filters/StringFilter.ts","../src/filters/EmaFilter.ts","../src/filters/OneEuroFilter.ts","../src/filters/LinearPredictionFilter.ts","../src/presets.ts","../src/kernels/gaussianKernel.ts","../src/kernels/boxKernel.ts","../src/kernels/triangleKernel.ts","../src/kernels/BilateralKernel.ts"],"sourcesContent":["import type { Point } from '../types'\n\n/**\n * Convolution kernel (fixed weights)\n */\nexport interface Kernel {\n readonly type: string\n readonly weights: number[]\n}\n\n/**\n * Adaptive kernel (dynamic weight computation)\n */\nexport interface AdaptiveKernel {\n readonly type: string\n readonly size: number\n computeWeights(center: Point, neighbors: Point[]): number[]\n}\n\n/**\n * All kernel types\n */\nexport type AnyKernel = Kernel | AdaptiveKernel\n\n/**\n * Check if kernel is adaptive\n */\nexport function isAdaptiveKernel(kernel: AnyKernel): kernel is AdaptiveKernel {\n return (\n 'computeWeights' in kernel && typeof kernel.computeWeights === 'function'\n )\n}\n\n/**\n * Padding mode\n * - 'reflect': Reflect at edges [3,2,1] | [1,2,3,4,5] | [5,4,3]\n * - 'edge': Repeat edge values [1,1,1] | [1,2,3,4,5] | [5,5,5]\n * - 'zero': Zero padding [0,0,0] | [1,2,3,4,5] | [0,0,0]\n */\nexport type PaddingMode = 'reflect' | 'edge' | 'zero'\n\nexport interface SmoothOptions {\n kernel: AnyKernel\n padding?: PaddingMode\n /** Preserve start and end points exactly (default: true) */\n preserveEndpoints?: boolean\n}\n","import type { Point } from './types'\nimport type { PaddingMode, SmoothOptions, Kernel } from './kernels/types'\nimport { isAdaptiveKernel } from './kernels/types'\n\n/**\n * Apply padding to extend point array\n */\nfunction applyPadding(\n points: Point[],\n halfSize: number,\n mode: PaddingMode\n): Point[] {\n if (points.length === 0) return []\n\n const padded: Point[] = []\n\n // Leading padding\n for (let i = halfSize; i > 0; i--) {\n switch (mode) {\n case 'reflect':\n padded.push(points[Math.min(i, points.length - 1)])\n break\n case 'edge':\n padded.push(points[0])\n break\n case 'zero':\n padded.push({ x: 0, y: 0 })\n break\n }\n }\n\n // Original data\n padded.push(...points)\n\n // Trailing padding\n for (let i = 1; i <= halfSize; i++) {\n switch (mode) {\n case 'reflect':\n padded.push(points[Math.max(0, points.length - 1 - i)])\n break\n case 'edge':\n padded.push(points[points.length - 1])\n break\n case 'zero':\n padded.push({ x: 0, y: 0 })\n break\n }\n }\n\n return padded\n}\n\n/**\n * Bidirectional convolution smoothing\n *\n * Supports both fixed-weight kernels (Gaussian, Box, Triangle) and\n * adaptive kernels (Bilateral).\n *\n * @example\n * ```ts\n * import { smooth, gaussianKernel, bilateralKernel } from '@stroke-stabilizer/core'\n *\n * // Standard convolution\n * const smoothed = smooth(points, {\n * kernel: gaussianKernel({ size: 7 }),\n * padding: 'reflect',\n * })\n *\n * // Bilateral (edge-preserving)\n * const edgePreserved = smooth(points, {\n * kernel: bilateralKernel({ size: 7, sigmaValue: 10 }),\n * padding: 'reflect',\n * })\n * ```\n */\nexport function smooth(points: Point[], options: SmoothOptions): Point[] {\n const { kernel, padding = 'reflect', preserveEndpoints = true } = options\n\n if (points.length === 0) return []\n\n // Save original endpoints if preserving\n const originalStart = points[0]\n const originalEnd = points[points.length - 1]\n\n let result: Point[]\n\n // Adaptive kernel (Bilateral, etc.)\n if (isAdaptiveKernel(kernel)) {\n const halfSize = Math.floor(kernel.size / 2)\n const padded = applyPadding(points, halfSize, padding)\n\n result = []\n\n for (let i = 0; i < points.length; i++) {\n const centerIdx = i + halfSize\n const center = padded[centerIdx]\n const neighbors: Point[] = []\n\n for (let k = 0; k < kernel.size; k++) {\n neighbors.push(padded[i + k])\n }\n\n const weights = kernel.computeWeights(center, neighbors)\n\n let sumX = 0\n let sumY = 0\n\n for (let k = 0; k < weights.length; k++) {\n sumX += neighbors[k].x * weights[k]\n sumY += neighbors[k].y * weights[k]\n }\n\n result.push({ x: sumX, y: sumY })\n }\n } else {\n // Fixed-weight kernel (Gaussian, Box, Triangle, etc.)\n const fixedKernel = kernel as Kernel\n const { weights } = fixedKernel\n\n if (weights.length <= 1) return [...points]\n\n const halfSize = Math.floor(weights.length / 2)\n const padded = applyPadding(points, halfSize, padding)\n\n result = []\n\n for (let i = 0; i < points.length; i++) {\n let sumX = 0\n let sumY = 0\n\n for (let k = 0; k < weights.length; k++) {\n const point = padded[i + k]\n sumX += point.x * weights[k]\n sumY += point.y * weights[k]\n }\n\n result.push({ x: sumX, y: sumY })\n }\n }\n\n // Restore original endpoints if preserving\n if (preserveEndpoints && result.length > 0) {\n result[0] = { ...originalStart }\n if (result.length > 1) {\n result[result.length - 1] = { ...originalEnd }\n }\n }\n\n return result\n}\n","import type { Filter, Point, PointerPoint, UpdatableFilter } from './types'\nimport type { Kernel, PaddingMode } from './kernels/types'\nimport { smooth } from './smooth'\n\n/**\n * Post-processor configuration\n */\ninterface PostProcessor {\n kernel: Kernel\n padding: PaddingMode\n}\n\n/**\n * Batch processing configuration\n */\nexport interface BatchConfig {\n /** Callback when a batch of points is processed */\n onBatch?: (points: PointerPoint[]) => void\n /** Callback for each processed point */\n onPoint?: (point: PointerPoint) => void\n}\n\n/**\n * StabilizedPointer configuration options\n */\nexport interface StabilizedPointerOptions {\n /**\n * Whether to append the raw endpoint when finishing a stroke.\n * This ensures the stabilized stroke ends at the actual input point.\n * @default true\n */\n appendEndpoint?: boolean\n}\n\n/**\n * Dynamic Pipeline Pattern implementation\n *\n * A pipeline that allows adding, removing, and updating filters at runtime.\n * Always ready to execute without requiring a .build() call.\n *\n * @example\n * ```ts\n * const pointer = new StabilizedPointer()\n * // Real-time layer\n * .addFilter(noiseFilter({ minDistance: 2 }))\n * .addFilter(kalmanFilter({ processNoise: 0.1 }))\n * // Post-processing layer\n * .addPostProcess(gaussianKernel({ size: 7 }))\n *\n * // During drawing\n * pointer.process(point)\n *\n * // On completion\n * const finalStroke = pointer.finish()\n * ```\n */\nexport class StabilizedPointer {\n private filters: Filter[] = []\n private postProcessors: PostProcessor[] = []\n private buffer: PointerPoint[] = []\n private lastRawPoint: PointerPoint | null = null\n\n // Configuration options\n private options: Required<StabilizedPointerOptions>\n\n // Batch processing fields\n private batchConfig: BatchConfig | null = null\n private pendingPoints: PointerPoint[] = []\n private rafId: number | null = null\n\n /**\n * Create a new StabilizedPointer\n * @param options Configuration options\n */\n constructor(options: StabilizedPointerOptions = {}) {\n this.options = {\n appendEndpoint: options.appendEndpoint ?? true,\n }\n }\n\n /**\n * Add a filter to the pipeline\n * @returns this (for method chaining)\n */\n addFilter(filter: Filter): this {\n this.filters.push(filter)\n return this\n }\n\n /**\n * Remove a filter by type\n * @returns true if removed, false if not found\n */\n removeFilter(type: string): boolean {\n const index = this.filters.findIndex((f) => f.type === type)\n if (index === -1) return false\n this.filters.splice(index, 1)\n return true\n }\n\n /**\n * Update parameters of a filter by type\n * @returns true if updated, false if not found\n */\n updateFilter<TParams>(type: string, params: Partial<TParams>): boolean {\n const filter = this.filters.find((f) => f.type === type)\n if (!filter) return false\n\n if (this.isUpdatableFilter<TParams>(filter)) {\n filter.updateParams(params)\n return true\n }\n return false\n }\n\n /**\n * Get a filter by type\n */\n getFilter(type: string): Filter | undefined {\n return this.filters.find((f) => f.type === type)\n }\n\n /**\n * Get list of current filter types\n */\n getFilterTypes(): string[] {\n return this.filters.map((f) => f.type)\n }\n\n /**\n * Check if a filter exists\n */\n hasFilter(type: string): boolean {\n return this.filters.some((f) => f.type === type)\n }\n\n /**\n * Process a point through the pipeline\n * @returns Processed result (null if rejected by a filter)\n */\n process(point: PointerPoint): PointerPoint | null {\n // Save raw input for convergence on finish\n this.lastRawPoint = point\n\n let current: PointerPoint | null = point\n\n for (const filter of this.filters) {\n if (current === null) break\n current = filter.process(current)\n }\n\n if (current !== null) {\n this.buffer.push(current)\n }\n\n return current\n }\n\n /**\n * Process multiple points at once\n * @returns Array of processed results (nulls excluded)\n */\n processAll(points: PointerPoint[]): PointerPoint[] {\n const results: PointerPoint[] = []\n for (const point of points) {\n const result = this.process(point)\n if (result !== null) {\n results.push(result)\n }\n }\n return results\n }\n\n /**\n * Get the processed buffer\n */\n getBuffer(): readonly PointerPoint[] {\n return this.buffer\n }\n\n /**\n * Clear and return the processed buffer\n */\n flushBuffer(): PointerPoint[] {\n const flushed = [...this.buffer]\n this.buffer = []\n return flushed\n }\n\n /**\n * Reset all filters and clear the buffer\n *\n * Call this to prepare for a new stroke without destroying the pipeline configuration.\n * Filters are reset to their initial state and the buffer is cleared.\n *\n * @example\n * ```ts\n * // After finishing a stroke\n * const result = pointer.finish() // automatically calls reset()\n *\n * // Or manually reset without finishing\n * pointer.reset()\n * ```\n */\n reset(): void {\n for (const filter of this.filters) {\n filter.reset()\n }\n this.buffer = []\n this.lastRawPoint = null\n this.hasDrained = false\n }\n\n /**\n * Clear the pipeline (remove all filters)\n */\n clear(): void {\n this.filters = []\n this.postProcessors = []\n this.buffer = []\n }\n\n /**\n * Get number of filters\n */\n get length(): number {\n return this.filters.length\n }\n\n // ========================================\n // Post-processing layer\n // ========================================\n\n /**\n * Add post-processor to the pipeline\n * @returns this (for method chaining)\n */\n addPostProcess(\n kernel: Kernel,\n options: { padding?: PaddingMode } = {}\n ): this {\n this.postProcessors.push({\n kernel,\n padding: options.padding ?? 'reflect',\n })\n return this\n }\n\n /**\n * Remove a post-processor by type\n * @returns true if removed, false if not found\n */\n removePostProcess(type: string): boolean {\n const index = this.postProcessors.findIndex((p) => p.kernel.type === type)\n if (index === -1) return false\n this.postProcessors.splice(index, 1)\n return true\n }\n\n /**\n * Check if a post-processor exists\n */\n hasPostProcess(type: string): boolean {\n return this.postProcessors.some((p) => p.kernel.type === type)\n }\n\n /**\n * Get list of post-processor types\n */\n getPostProcessTypes(): string[] {\n return this.postProcessors.map((p) => p.kernel.type)\n }\n\n /**\n * Get number of post-processors\n */\n get postProcessLength(): number {\n return this.postProcessors.length\n }\n\n /**\n * Finish the stroke and return post-processed results, without resetting\n *\n * Use this when you want to get the final result but keep the buffer intact.\n * Useful for previewing post-processing results or comparing different settings.\n *\n * @example\n * ```ts\n * pointer.addPostProcess(gaussianKernel({ size: 5 }))\n * const preview1 = pointer.finishWithoutReset()\n *\n * // Change settings and re-apply\n * pointer.removePostProcess('gaussian')\n * pointer.addPostProcess(bilateralKernel({ size: 7, sigmaValue: 10 }))\n * const preview2 = pointer.finishWithoutReset()\n *\n * // Finalize when done\n * const final = pointer.finish()\n * ```\n */\n finishWithoutReset(): Point[] {\n // Flush any pending batched points first\n if (this.batchConfig) {\n this.flushBatch()\n }\n\n // Append endpoint to ensure stroke ends at actual input point\n this.appendEndpoint()\n\n let points: Point[] = [...this.buffer]\n\n // Apply post-processors in order\n for (const processor of this.postProcessors) {\n points = smooth(points, {\n kernel: processor.kernel,\n padding: processor.padding,\n })\n }\n\n return points\n }\n\n /**\n * Track if drain has been called to avoid duplicate draining\n */\n private hasDrained = false\n\n /**\n * Append the final raw input point to ensure stroke ends at the actual endpoint\n *\n * Instead of draining filters (which adds many extra points),\n * we simply append the raw endpoint. The post-processing phase\n * with bidirectional convolution will naturally smooth the transition.\n */\n private appendEndpoint(): void {\n // Skip if disabled via options\n if (!this.options.appendEndpoint) {\n return\n }\n // Avoid duplicate appending (finishWithoutReset may be called multiple times)\n if (this.hasDrained) {\n return\n }\n if (this.lastRawPoint === null || this.buffer.length === 0) {\n return\n }\n\n this.hasDrained = true\n\n const target = this.lastRawPoint\n const lastBufferPoint = this.buffer[this.buffer.length - 1]\n\n // Calculate distance between last stabilized point and target\n const dx = target.x - lastBufferPoint.x\n const dy = target.y - lastBufferPoint.y\n const distance = Math.sqrt(dx * dx + dy * dy)\n\n // If already close enough, no need to append\n if (distance < 1) {\n return\n }\n\n // Append the raw endpoint directly to the buffer\n // Post-processing (Gaussian, etc.) will smooth the transition\n this.buffer.push({\n x: target.x,\n y: target.y,\n pressure: target.pressure ?? 1,\n timestamp: target.timestamp + 8,\n })\n }\n\n /**\n * Finish the stroke and return post-processed results\n *\n * This applies all post-processors to the buffer, then resets filters and clears the buffer.\n * Use this at the end of a stroke to get the final smoothed result.\n *\n * @example\n * ```ts\n * // During drawing\n * pointer.process(point)\n *\n * // On stroke end\n * const finalStroke = pointer.finish()\n * ```\n */\n finish(): Point[] {\n const points = this.finishWithoutReset()\n this.reset()\n return points\n }\n\n // ========================================\n // Batch processing layer (rAF)\n // ========================================\n\n /**\n * Enable requestAnimationFrame batch processing\n *\n * When enabled, points queued via queue() are batched and processed\n * on the next animation frame, reducing CPU load for high-frequency\n * pointer events.\n *\n * @returns this (for method chaining)\n *\n * @example\n * ```ts\n * const pointer = new StabilizedPointer()\n * .addFilter(noiseFilter({ minDistance: 2 }))\n * .enableBatching({\n * onBatch: (points) => drawPoints(points),\n * onPoint: (point) => updatePreview(point)\n * })\n *\n * canvas.onpointermove = (e) => {\n * pointer.queue({ x: e.clientX, y: e.clientY, timestamp: e.timeStamp })\n * }\n * ```\n */\n enableBatching(config: BatchConfig = {}): this {\n this.batchConfig = config\n return this\n }\n\n /**\n * Disable batch processing\n * Flushes any pending points before disabling\n * @returns this (for method chaining)\n */\n disableBatching(): this {\n this.flushBatch()\n this.batchConfig = null\n return this\n }\n\n /**\n * Check if batch processing is enabled\n */\n get isBatchingEnabled(): boolean {\n return this.batchConfig !== null\n }\n\n /**\n * Queue a point for batch processing\n *\n * If batching is enabled, the point is queued and processed on the next\n * animation frame. If batching is disabled, the point is processed immediately.\n *\n * @returns this (for method chaining, useful for queueing multiple points)\n */\n queue(point: PointerPoint): this {\n if (this.batchConfig) {\n this.pendingPoints.push(point)\n this.scheduleFlush()\n } else {\n // Fallback to immediate processing\n this.process(point)\n }\n return this\n }\n\n /**\n * Queue multiple points for batch processing\n * @returns this (for method chaining)\n */\n queueAll(points: PointerPoint[]): this {\n if (this.batchConfig) {\n this.pendingPoints.push(...points)\n this.scheduleFlush()\n } else {\n this.processAll(points)\n }\n return this\n }\n\n /**\n * Force flush pending batched points immediately\n * @returns Array of processed points\n */\n flushBatch(): PointerPoint[] {\n this.cancelScheduledFlush()\n return this.processPendingPoints()\n }\n\n /**\n * Get number of pending points in the batch queue\n */\n get pendingCount(): number {\n return this.pendingPoints.length\n }\n\n // ----------------------------------------\n // Private batch processing methods\n // ----------------------------------------\n\n private scheduleFlush(): void {\n if (this.rafId !== null) return\n\n // Use rAF if available, otherwise use setTimeout as fallback\n if (typeof requestAnimationFrame !== 'undefined') {\n this.rafId = requestAnimationFrame(() => {\n this.rafId = null\n this.processPendingPoints()\n })\n } else {\n // Node.js or non-browser environment fallback\n this.rafId = setTimeout(() => {\n this.rafId = null\n this.processPendingPoints()\n }, 16) as unknown as number\n }\n }\n\n private cancelScheduledFlush(): void {\n if (this.rafId === null) return\n\n if (typeof cancelAnimationFrame !== 'undefined') {\n cancelAnimationFrame(this.rafId)\n } else {\n clearTimeout(this.rafId)\n }\n this.rafId = null\n }\n\n private processPendingPoints(): PointerPoint[] {\n if (this.pendingPoints.length === 0) return []\n\n const points = this.pendingPoints\n this.pendingPoints = []\n\n const results: PointerPoint[] = []\n\n for (const point of points) {\n const result = this.process(point)\n if (result !== null) {\n results.push(result)\n // Call onPoint callback for each processed point\n this.batchConfig?.onPoint?.(result)\n }\n }\n\n // Call onBatch callback with all processed points\n if (results.length > 0) {\n this.batchConfig?.onBatch?.(results)\n }\n\n return results\n }\n\n private isUpdatableFilter<TParams>(\n filter: Filter\n ): filter is UpdatableFilter<TParams> {\n return 'updateParams' in filter && typeof filter.updateParams === 'function'\n }\n}\n","import type { Filter, PointerPoint, UpdatableFilter } from '../types'\n\nexport interface NoiseFilterParams {\n /** Minimum movement distance (px). Movements smaller than this are rejected */\n minDistance: number\n}\n\nconst FILTER_TYPE = 'noise' as const\n\n/**\n * Noise filter\n *\n * Rejects points to eliminate jitter if the distance from the\n * previous point is less than minDistance.\n */\nclass NoiseFilterImpl implements UpdatableFilter<NoiseFilterParams> {\n readonly type = FILTER_TYPE\n private params: NoiseFilterParams\n private lastPoint: PointerPoint | null = null\n\n constructor(params: NoiseFilterParams) {\n this.params = { ...params }\n }\n\n process(point: PointerPoint): PointerPoint | null {\n if (this.lastPoint === null) {\n this.lastPoint = point\n return point\n }\n\n const dx = point.x - this.lastPoint.x\n const dy = point.y - this.lastPoint.y\n const distance = Math.sqrt(dx * dx + dy * dy)\n\n if (distance < this.params.minDistance) {\n return null // Reject\n }\n\n this.lastPoint = point\n return point\n }\n\n updateParams(params: Partial<NoiseFilterParams>): void {\n this.params = { ...this.params, ...params }\n }\n\n reset(): void {\n this.lastPoint = null\n }\n}\n\n/**\n * Create a noise filter\n *\n * @example\n * ```ts\n * const pointer = new StabilizedPointer()\n * .addFilter(noiseFilter({ minDistance: 2 }))\n * ```\n */\nexport function noiseFilter(params: NoiseFilterParams): Filter {\n return new NoiseFilterImpl(params)\n}\n","import type { Filter, PointerPoint, UpdatableFilter } from '../types'\n\nexport interface KalmanFilterParams {\n /** Process noise (Q): Lower values trust prediction more */\n processNoise: number\n /** Measurement noise (R): Higher values result in stronger smoothing */\n measurementNoise: number\n}\n\ninterface KalmanState {\n x: number\n y: number\n p: number // covariance\n lastTimestamp: number\n}\n\nconst FILTER_TYPE = 'kalman' as const\n\n/**\n * Kalman filter\n *\n * Simple position-only Kalman filter for smooth cursor tracking.\n * Uses prediction from previous position (no velocity) to avoid runaway behavior.\n */\nclass KalmanFilterImpl implements UpdatableFilter<KalmanFilterParams> {\n readonly type = FILTER_TYPE\n private params: KalmanFilterParams\n private state: KalmanState | null = null\n\n constructor(params: KalmanFilterParams) {\n this.params = { ...params }\n }\n\n process(point: PointerPoint): PointerPoint | null {\n if (this.state === null) {\n this.state = {\n x: point.x,\n y: point.y,\n p: 1,\n lastTimestamp: point.timestamp,\n }\n return point\n }\n\n const { processNoise: Q, measurementNoise: R } = this.params\n\n // Prediction step: assume position stays the same (no velocity model)\n const predictedX = this.state.x\n const predictedY = this.state.y\n const predictedP = this.state.p + Q\n\n // Update step (calculate Kalman gain)\n const K = predictedP / (predictedP + R)\n\n // State update: blend prediction with measurement\n const newX = predictedX + K * (point.x - predictedX)\n const newY = predictedY + K * (point.y - predictedY)\n const newP = (1 - K) * predictedP\n\n this.state = {\n x: newX,\n y: newY,\n p: newP,\n lastTimestamp: point.timestamp,\n }\n\n return {\n x: newX,\n y: newY,\n pressure: point.pressure,\n timestamp: point.timestamp,\n }\n }\n\n updateParams(params: Partial<KalmanFilterParams>): void {\n this.params = { ...this.params, ...params }\n }\n\n reset(): void {\n this.state = null\n }\n}\n\n/**\n * Create a Kalman filter\n *\n * @example\n * ```ts\n * const pointer = new StabilizedPointer()\n * .addFilter(kalmanFilter({\n * processNoise: 0.1,\n * measurementNoise: 0.5\n * }))\n * ```\n */\nexport function kalmanFilter(params: KalmanFilterParams): Filter {\n return new KalmanFilterImpl(params)\n}\n","import type { Filter, PointerPoint, UpdatableFilter } from '../types'\n\nexport interface MovingAverageFilterParams {\n /** Number of points to average */\n windowSize: number\n}\n\nconst FILTER_TYPE = 'movingAverage' as const\n\n/**\n * Moving average filter\n *\n * Smooths by averaging the last N points.\n * Simpler and faster than Gaussian filter.\n */\nclass MovingAverageFilterImpl implements UpdatableFilter<MovingAverageFilterParams> {\n readonly type = FILTER_TYPE\n private params: MovingAverageFilterParams\n private window: PointerPoint[] = []\n\n constructor(params: MovingAverageFilterParams) {\n this.params = { ...params }\n }\n\n process(point: PointerPoint): PointerPoint | null {\n this.window.push(point)\n\n // Remove old points when exceeding window size\n while (this.window.length > this.params.windowSize) {\n this.window.shift()\n }\n\n // Calculate average\n let sumX = 0\n let sumY = 0\n let sumPressure = 0\n let pressureCount = 0\n\n for (const p of this.window) {\n sumX += p.x\n sumY += p.y\n if (p.pressure !== undefined) {\n sumPressure += p.pressure\n pressureCount++\n }\n }\n\n const avgX = sumX / this.window.length\n const avgY = sumY / this.window.length\n const avgPressure =\n pressureCount > 0 ? sumPressure / pressureCount : undefined\n\n return {\n x: avgX,\n y: avgY,\n pressure: avgPressure,\n timestamp: point.timestamp,\n }\n }\n\n updateParams(params: Partial<MovingAverageFilterParams>): void {\n this.params = { ...this.params, ...params }\n // Remove old points if window size decreased\n while (this.window.length > this.params.windowSize) {\n this.window.shift()\n }\n }\n\n reset(): void {\n this.window = []\n }\n}\n\n/**\n * Create a moving average filter\n *\n * @example\n * ```ts\n * const pointer = new StabilizedPointer()\n * .addFilter(movingAverageFilter({ windowSize: 5 }))\n * ```\n */\nexport function movingAverageFilter(params: MovingAverageFilterParams): Filter {\n return new MovingAverageFilterImpl(params)\n}\n","import type { Filter, PointerPoint, UpdatableFilter } from '../types'\n\nexport interface StringFilterParams {\n /** String length (px): Dead zone radius */\n stringLength: number\n}\n\nconst FILTER_TYPE = 'string' as const\n\n/**\n * String stabilization filter (Lazy Brush / String Stabilization)\n *\n * Behaves as if there's a virtual \"string\" between the pen tip and drawing point.\n * Drawing point doesn't move within the string length, but gets pulled when exceeded.\n */\nclass StringFilterImpl implements UpdatableFilter<StringFilterParams> {\n readonly type = FILTER_TYPE\n private params: StringFilterParams\n private anchorPoint: PointerPoint | null = null\n\n constructor(params: StringFilterParams) {\n this.params = { ...params }\n }\n\n process(point: PointerPoint): PointerPoint | null {\n if (this.anchorPoint === null) {\n this.anchorPoint = point\n return point\n }\n\n const dx = point.x - this.anchorPoint.x\n const dy = point.y - this.anchorPoint.y\n const distance = Math.sqrt(dx * dx + dy * dy)\n\n // Within string length, return anchor point (no movement)\n if (distance <= this.params.stringLength) {\n return {\n ...this.anchorPoint,\n pressure: point.pressure,\n timestamp: point.timestamp,\n }\n }\n\n // Move by the amount exceeding string length\n const ratio = (distance - this.params.stringLength) / distance\n const newX = this.anchorPoint.x + dx * ratio\n const newY = this.anchorPoint.y + dy * ratio\n\n this.anchorPoint = {\n x: newX,\n y: newY,\n pressure: point.pressure,\n timestamp: point.timestamp,\n }\n\n return this.anchorPoint\n }\n\n updateParams(params: Partial<StringFilterParams>): void {\n this.params = { ...this.params, ...params }\n }\n\n reset(): void {\n this.anchorPoint = null\n }\n}\n\n/**\n * Create a string stabilization filter\n *\n * @example\n * ```ts\n * const pointer = new StabilizedPointer()\n * .addFilter(stringFilter({ stringLength: 10 }))\n * ```\n */\nexport function stringFilter(params: StringFilterParams): Filter {\n return new StringFilterImpl(params)\n}\n","import type { Filter, PointerPoint, UpdatableFilter } from '../types'\n\nexport interface EmaFilterParams {\n /**\n * Smoothing coefficient (0-1)\n * - Lower value: stronger smoothing (emphasizes past values)\n * - Higher value: more responsive (emphasizes new values)\n */\n alpha: number\n}\n\nconst FILTER_TYPE = 'ema' as const\n\n/**\n * Exponential Moving Average (EMA) filter\n *\n * IIR filter. Weights newer values more heavily, older values decay exponentially.\n * Lowest computational cost and minimal latency.\n *\n * Formula: y[n] = α * x[n] + (1 - α) * y[n-1]\n */\nclass EmaFilterImpl implements UpdatableFilter<EmaFilterParams> {\n readonly type = FILTER_TYPE\n private params: EmaFilterParams\n private lastPoint: PointerPoint | null = null\n\n constructor(params: EmaFilterParams) {\n this.params = { ...params }\n }\n\n process(point: PointerPoint): PointerPoint | null {\n if (this.lastPoint === null) {\n this.lastPoint = point\n return point\n }\n\n const { alpha } = this.params\n\n // EMA: y = α * x + (1 - α) * y_prev\n const newX = alpha * point.x + (1 - alpha) * this.lastPoint.x\n const newY = alpha * point.y + (1 - alpha) * this.lastPoint.y\n\n // Apply EMA to pressure if present\n let newPressure: number | undefined\n if (point.pressure !== undefined && this.lastPoint.pressure !== undefined) {\n newPressure =\n alpha * point.pressure + (1 - alpha) * this.lastPoint.pressure\n } else {\n newPressure = point.pressure\n }\n\n this.lastPoint = {\n x: newX,\n y: newY,\n pressure: newPressure,\n timestamp: point.timestamp,\n }\n\n return this.lastPoint\n }\n\n updateParams(params: Partial<EmaFilterParams>): void {\n this.params = { ...this.params, ...params }\n }\n\n reset(): void {\n this.lastPoint = null\n }\n}\n\n/**\n * Create an Exponential Moving Average (EMA) filter\n *\n * @example\n * ```ts\n * // Strong smoothing\n * const pointer = new StabilizedPointer()\n * .addFilter(emaFilter({ alpha: 0.2 }))\n *\n * // Light smoothing\n * const pointer = new StabilizedPointer()\n * .addFilter(emaFilter({ alpha: 0.7 }))\n * ```\n */\nexport function emaFilter(params: EmaFilterParams): Filter {\n return new EmaFilterImpl(params)\n}\n","import type { Filter, PointerPoint, UpdatableFilter } from '../types'\n\nexport interface OneEuroFilterParams {\n /**\n * Minimum cutoff frequency (Hz)\n * Smoothing strength at low speed. Lower = smoother.\n * Recommended: 0.5 - 2.0\n */\n minCutoff: number\n /**\n * Speed coefficient\n * Rate of cutoff frequency increase based on speed.\n * Higher = more responsive at high speed.\n * Recommended: 0.001 - 0.01\n */\n beta: number\n /**\n * Derivative cutoff frequency (Hz)\n * Smoothing for velocity estimation. Usually fixed at 1.0.\n */\n dCutoff?: number\n}\n\nconst FILTER_TYPE = 'oneEuro' as const\n\n/**\n * Low-pass filter (internal use)\n */\nclass LowPassFilter {\n private y: number | null = null\n private alpha: number = 1\n\n setAlpha(alpha: number): void {\n this.alpha = Math.max(0, Math.min(1, alpha))\n }\n\n filter(value: number): number {\n if (this.y === null) {\n this.y = value\n } else {\n this.y = this.alpha * value + (1 - this.alpha) * this.y\n }\n return this.y\n }\n\n reset(): void {\n this.y = null\n }\n\n lastValue(): number | null {\n return this.y\n }\n}\n\n/**\n * One Euro Filter\n *\n * Speed-adaptive low-pass filter.\n * - At low speed: Strong smoothing (jitter removal)\n * - At high speed: Light smoothing (reduce latency)\n *\n * Paper: https://cristal.univ-lille.fr/~casiez/1euro/\n */\nclass OneEuroFilterImpl implements UpdatableFilter<OneEuroFilterParams> {\n readonly type = FILTER_TYPE\n private params: OneEuroFilterParams\n\n private xFilter = new LowPassFilter()\n private yFilter = new LowPassFilter()\n private dxFilter = new LowPassFilter()\n private dyFilter = new LowPassFilter()\n private pressureFilter = new LowPassFilter()\n\n private lastTimestamp: number | null = null\n\n constructor(params: OneEuroFilterParams) {\n this.params = {\n dCutoff: 1.0,\n ...params,\n }\n }\n\n process(point: PointerPoint): PointerPoint | null {\n // Calculate sampling rate\n let rate = 60 // Default 60Hz\n if (this.lastTimestamp !== null) {\n const dt = (point.timestamp - this.lastTimestamp) / 1000\n if (dt > 0) {\n rate = 1 / dt\n }\n }\n this.lastTimestamp = point.timestamp\n\n const { minCutoff, beta, dCutoff } = this.params\n\n // Process X axis\n const newX = this.filterAxis(\n point.x,\n this.xFilter,\n this.dxFilter,\n rate,\n minCutoff,\n beta,\n dCutoff!\n )\n\n // Process Y axis\n const newY = this.filterAxis(\n point.y,\n this.yFilter,\n this.dyFilter,\n rate,\n minCutoff,\n beta,\n dCutoff!\n )\n\n // Process pressure (if present)\n let newPressure: number | undefined\n if (point.pressure !== undefined) {\n // Pressure uses simple EMA without speed adaptation\n const alpha = this.computeAlpha(minCutoff, rate)\n this.pressureFilter.setAlpha(alpha)\n newPressure = this.pressureFilter.filter(point.pressure)\n }\n\n return {\n x: newX,\n y: newY,\n pressure: newPressure,\n timestamp: point.timestamp,\n }\n }\n\n private filterAxis(\n value: number,\n valueFilter: LowPassFilter,\n derivFilter: LowPassFilter,\n rate: number,\n minCutoff: number,\n beta: number,\n dCutoff: number\n ): number {\n // Get previous value\n const prevValue = valueFilter.lastValue()\n\n // Calculate derivative (velocity)\n let dValue = 0\n if (prevValue !== null) {\n dValue = (value - prevValue) * rate\n }\n\n // Filter the derivative\n const dAlpha = this.computeAlpha(dCutoff, rate)\n derivFilter.setAlpha(dAlpha)\n const filteredDValue = derivFilter.filter(dValue)\n\n // Adjust cutoff frequency based on speed\n const cutoff = minCutoff + beta * Math.abs(filteredDValue)\n\n // Filter the value\n const alpha = this.computeAlpha(cutoff, rate)\n valueFilter.setAlpha(alpha)\n return valueFilter.filter(value)\n }\n\n private computeAlpha(cutoff: number, rate: number): number {\n const tau = 1 / (2 * Math.PI * cutoff)\n const te = 1 / rate\n return 1 / (1 + tau / te)\n }\n\n updateParams(params: Partial<OneEuroFilterParams>): void {\n this.params = { ...this.params, ...params }\n }\n\n reset(): void {\n this.xFilter.reset()\n this.yFilter.reset()\n this.dxFilter.reset()\n this.dyFilter.reset()\n this.pressureFilter.reset()\n this.lastTimestamp = null\n }\n}\n\n/**\n * Create a One Euro Filter\n *\n * Speed-adaptive low-pass filter.\n * Strong smoothing at low speed, responsive at high speed.\n *\n * @example\n * ```ts\n * // Balanced settings\n * const pointer = new StabilizedPointer()\n * .addFilter(oneEuroFilter({\n * minCutoff: 1.0,\n * beta: 0.007\n * }))\n *\n * // Smoother (higher latency)\n * const smooth = oneEuroFilter({ minCutoff: 0.5, beta: 0.001 })\n *\n * // More responsive (may have jitter)\n * const responsive = oneEuroFilter({ minCutoff: 2.0, beta: 0.01 })\n * ```\n */\nexport function oneEuroFilter(params: OneEuroFilterParams): Filter {\n return new OneEuroFilterImpl(params)\n}\n","import type { Filter, PointerPoint, UpdatableFilter } from '../types'\n\nexport interface LinearPredictionFilterParams {\n /**\n * Number of points used for prediction\n * More points = more stable but higher computation cost\n * Recommended: 3-5\n */\n historySize: number\n /**\n * Prediction strength (0-1)\n * - 0: No prediction (returns current position as-is)\n * - 1: Full prediction (maximally considers velocity/acceleration)\n * Recommended: 0.3-0.7\n */\n predictionFactor: number\n /**\n * Smoothing coefficient (0-1)\n * EMA coefficient applied to prediction output\n * Recommended: 0.5-0.8\n */\n smoothing?: number\n}\n\nconst FILTER_TYPE = 'linearPrediction' as const\n\n/**\n * Linear prediction filter\n *\n * Compensates for filter-induced latency by calculating velocity and\n * acceleration from past positions to predict the next position.\n *\n * Method:\n * 1. Estimate velocity and acceleration from past N points using least squares\n * 2. Predicted position = current position + velocity * Δt + 0.5 * acceleration * Δt²\n * 3. Blend current position and predicted position using prediction factor\n */\nclass LinearPredictionFilterImpl implements UpdatableFilter<LinearPredictionFilterParams> {\n readonly type = FILTER_TYPE\n private params: LinearPredictionFilterParams\n private history: PointerPoint[] = []\n private lastOutput: PointerPoint | null = null\n\n constructor(params: LinearPredictionFilterParams) {\n this.params = {\n smoothing: 0.6,\n ...params,\n }\n }\n\n process(point: PointerPoint): PointerPoint | null {\n this.history.push(point)\n\n // Remove old entries when history exceeds historySize+1\n while (this.history.length > this.params.historySize + 1) {\n this.history.shift()\n }\n\n // Return first point as-is\n if (this.history.length === 1) {\n this.lastOutput = point\n return point\n }\n\n // Estimate velocity and acceleration\n const { velocity, acceleration } = this.estimateMotion()\n\n // Calculate time delta (in seconds)\n const dt =\n this.history.length >= 2\n ? (this.history[this.history.length - 1].timestamp -\n this.history[this.history.length - 2].timestamp) /\n 1000\n : 1 / 60 // Default 60fps\n\n // Calculate predicted position\n const { predictionFactor } = this.params\n const predictedX =\n point.x +\n velocity.x * dt * predictionFactor +\n 0.5 * acceleration.x * dt * dt * predictionFactor\n const predictedY =\n point.y +\n velocity.y * dt * predictionFactor +\n 0.5 * acceleration.y * dt * dt * predictionFactor\n\n // Apply smoothing\n let outputX = predictedX\n let outputY = predictedY\n let outputPressure = point.pressure\n\n if (this.lastOutput !== null && this.params.smoothing !== undefined) {\n const s = this.params.smoothing\n outputX = s * predictedX + (1 - s) * this.lastOutput.x\n outputY = s * predictedY + (1 - s) * this.lastOutput.y\n\n if (\n point.pressure !== undefined &&\n this.lastOutput.pressure !== undefined\n ) {\n outputPressure = s * point.pressure + (1 - s) * this.lastOutput.pressure\n }\n }\n\n this.lastOutput = {\n x: outputX,\n y: outputY,\n pressure: outputPressure,\n timestamp: point.timestamp,\n }\n\n return this.lastOutput\n }\n\n /**\n * Estimate velocity and acceleration using least squares\n */\n private estimateMotion(): {\n velocity: { x: number; y: number }\n acceleration: { x: number; y: number }\n } {\n const n = this.history.length\n if (n < 2) {\n return {\n velocity: { x: 0, y: 0 },\n acceleration: { x: 0, y: 0 },\n }\n }\n\n // Normalize time (first point = 0)\n const t0 = this.history[0].timestamp\n const times = this.history.map((p) => (p.timestamp - t0) / 1000)\n const xs = this.history.map((p) => p.x)\n const ys = this.history.map((p) => p.y)\n\n if (n === 2) {\n // Simple velocity calculation for 2 points\n const dt = times[1] - times[0]\n if (dt <= 0) {\n return { velocity: { x: 0, y: 0 }, acceleration: { x: 0, y: 0 } }\n }\n return {\n velocity: {\n x: (xs[1] - xs[0]) / dt,\n y: (ys[1] - ys[0]) / dt,\n },\n acceleration: { x: 0, y: 0 },\n }\n }\n\n // For 3+ points, use quadratic polynomial fitting\n // x(t) = a + b*t + c*t^2\n // velocity = b + 2*c*t\n // acceleration = 2*c\n\n const fitX = this.polynomialFit(times, xs)\n const fitY = this.polynomialFit(times, ys)\n\n const lastT = times[times.length - 1]\n\n return {\n velocity: {\n x: fitX.b + 2 * fitX.c * lastT,\n y: fitY.b + 2 * fitY.c * lastT,\n },\n acceleration: {\n x: 2 * fitX.c,\n y: 2 * fitY.c,\n },\n }\n }\n\n /**\n * Quadratic polynomial least squares fitting\n * y = a + b*x + c*x^2\n */\n private polynomialFit(\n x: number[],\n y: number[]\n ): { a: number; b: number; c: number } {\n const n = x.length\n\n // Build normal equation coefficient matrix\n let sumX = 0,\n sumX2 = 0,\n sumX3 = 0,\n sumX4 = 0\n let sumY = 0,\n sumXY = 0,\n sumX2Y = 0\n\n for (let i = 0; i < n; i++) {\n const xi = x[i]\n const yi = y[i]\n const xi2 = xi * xi\n sumX += xi\n sumX2 += xi2\n sumX3 += xi2 * xi\n sumX4 += xi2 * xi2\n sumY += yi\n sumXY += xi * yi\n sumX2Y += xi2 * yi\n }\n\n // Solve system of equations (Cramer's rule)\n // [n, sumX, sumX2 ] [a] [sumY ]\n // [sumX, sumX2, sumX3 ] [b] = [sumXY ]\n // [sumX2, sumX3, sumX4 ] [c] [sumX2Y]\n\n const det =\n n * (sumX2 * sumX4 - sumX3 * sumX3) -\n sumX * (sumX * sumX4 - sumX3 * sumX2) +\n sumX2 * (sumX * sumX3 - sumX2 * sumX2)\n\n if (Math.abs(det) < 1e-10) {\n // Fall back to linear fit if matrix is singular\n const avgX = sumX / n\n const avgY = sumY / n\n let num = 0,\n den = 0\n for (let i = 0; i < n; i++) {\n num += (x[i] - avgX) * (y[i] - avgY)\n den += (x[i] - avgX) * (x[i] - avgX)\n }\n const b = den > 0 ? num / den : 0\n const a = avgY - b * avgX\n return { a, b, c: 0 }\n }\n\n const a =\n (sumY * (sumX2 * sumX4 - sumX3 * sumX3) -\n sumX * (sumXY * sumX4 - sumX3 * sumX2Y) +\n sumX2 * (sumXY * sumX3 - sumX2 * sumX2Y)) /\n det\n\n const b =\n (n * (sumXY * sumX4 - sumX3 * sumX2Y) -\n sumY * (sumX * sumX4 - sumX3 * sumX2) +\n sumX2 * (sumX * sumX2Y - sumXY * sumX2)) /\n det\n\n const c =\n (n * (sumX2 * sumX2Y - sumXY * sumX3) -\n sumX * (sumX * sumX2Y - sumXY * sumX2) +\n sumY * (sumX * sumX3 - sumX2 * sumX2)) /\n det\n\n return { a, b, c }\n }\n\n updateParams(params: Partial<LinearPredictionFilterParams>): void {\n this.params = { ...this.params, ...params }\n }\n\n reset(): void {\n this.history = []\n this.lastOutput = null\n }\n}\n\n/**\n * Create a linear prediction filter\n *\n * Compensates for filter-induced latency by estimating velocity and\n * acceleration from past positions to predict the next position.\n *\n * @example\n * ```ts\n * // Standard settings\n * const pointer = new StabilizedPointer()\n * .addFilter(linearPredictionFilter({\n * historySize: 4,\n * predictionFactor: 0.5\n * }))\n *\n * // Strong prediction (prioritize latency reduction)\n * const responsive = linearPredictionFilter({\n * historySize: 3,\n * predictionFactor: 0.8,\n * smoothing: 0.7\n * })\n *\n * // Stability focused\n * const stable = linearPredictionFilter({\n * historySize: 5,\n * predictionFactor: 0.3,\n * smoothing: 0.5\n * })\n * ```\n */\nexport function linearPredictionFilter(\n params: LinearPredictionFilterParams\n): Filter {\n return new LinearPredictionFilterImpl(params)\n}\n","import { StabilizedPointer } from './StabilizedPointer'\nimport { noiseFilter } from './filters/NoiseFilter'\nimport { kalmanFilter } from './filters/KalmanFilter'\nimport { movingAverageFilter } from './filters/MovingAverageFilter'\nimport { stringFilter } from './filters/StringFilter'\n\n/**\n * Create a StabilizedPointer based on level (0-100)\n *\n * Filter configuration by level:\n * - 0%: No stabilization\n * - 1-20%: Noise filter only\n * - 21-40%: Noise + Kalman\n * - 41-60%: Noise + Kalman + Moving average\n * - 61-80%: Above + String (light)\n * - 81-100%: Above + String (strong)\n *\n * @example\n * ```ts\n * // Medium stabilization\n * const pointer = createStabilizedPointer(50)\n *\n * // No stabilization\n * const raw = createStabilizedPointer(0)\n * ```\n */\nexport function createStabilizedPointer(level: number): StabilizedPointer {\n const clampedLevel = Math.max(0, Math.min(100, level))\n const pointer = new StabilizedPointer()\n\n if (clampedLevel === 0) {\n return pointer\n }\n\n // Scale: level 50 = max effect (t=1.0), level 100 = same as level 50\n const t = Math.min(clampedLevel / 50, 1.0)\n\n // Level 1-100: Noise filter\n const minDistance = 1.0 + t * 2.0 // 1.0-3.0\n pointer.addFilter(noiseFilter({ minDistance }))\n\n if (clampedLevel >= 21) {\n // Level 21-100: Kalman filter\n const processNoise = 0.12 - t * 0.08 // 0.12-0.04\n const measurementNoise = 0.4 + t * 0.6 // 0.4-1.0\n pointer.addFilter(kalmanFilter({ processNoise, measurementNoise }))\n }\n\n if (clampedLevel >= 41) {\n // Level 41-100: Moving average filter\n const windowSize = clampedLevel >= 61 ? 7 : 5\n pointer.addFilter(movingAverageFilter({ windowSize }))\n }\n\n if (clampedLevel >= 61) {\n // Level 61-100: String stabilization\n const stringLength = clampedLevel >= 81 ? 15 : 8\n pointer.addFilter(stringFilter({ stringLength }))\n }\n\n return pointer\n}\n\n/**\n * Create StabilizedPointer from preset name\n */\nexport type PresetName = 'none' | 'light' | 'medium' | 'heavy' | 'extreme'\n\nconst presetLevels: Record<PresetName, number> = {\n none: 0,\n light: 20,\n medium: 50,\n heavy: 75,\n extreme: 100,\n}\n\n/**\n * Create StabilizedPointer from preset\n *\n * @example\n * ```ts\n * const pointer = createFromPreset('medium')\n * ```\n */\nexport function createFromPreset(preset: PresetName): StabilizedPointer {\n return createStabilizedPointer(presetLevels[preset])\n}\n","import type { Kernel } from './types'\n\nexport interface GaussianKernelParams {\n /** Kernel size (odd number) */\n size: number\n /** Standard deviation (default: size / 3) */\n sigma?: number\n}\n\n/**\n * Generate a Gaussian kernel\n *\n * @example\n * ```ts\n * const kernel = gaussianKernel({ size: 7, sigma: 2 })\n * ```\n */\nexport function gaussianKernel(params: GaussianKernelParams): Kernel {\n const { size } = params\n const sigma = params.sigma ?? size / 3\n\n // Force odd size\n const actualSize = size % 2 === 0 ? size + 1 : size\n const halfSize = Math.floor(actualSize / 2)\n\n const weights: number[] = []\n let sum = 0\n\n for (let i = 0; i < actualSize; i++) {\n const x = i - halfSize\n const weight = Math.exp(-(x * x) / (2 * sigma * sigma))\n weights.push(weight)\n sum += weight\n }\n\n // Normalize\n for (let i = 0; i < weights.length; i++) {\n weights[i] /= sum\n }\n\n return {\n type: 'gaussian',\n weights,\n }\n}\n","import type { Kernel } from './types'\n\nexport interface BoxKernelParams {\n /** Kernel size (odd number) */\n size: number\n}\n\n/**\n * Generate a box kernel (simple average)\n *\n * @example\n * ```ts\n * const kernel = boxKernel({ size: 5 })\n * // weights: [0.2, 0.2, 0.2, 0.2, 0.2]\n * ```\n */\nexport function boxKernel(params: BoxKernelParams): Kernel {\n const { size } = params\n\n // Force odd size\n const actualSize = size % 2 === 0 ? size + 1 : size\n const weight = 1 / actualSize\n\n const weights = Array(actualSize).fill(weight)\n\n return {\n type: 'box',\n weights,\n }\n}\n","import type { Kernel } from './types'\n\nexport interface TriangleKernelParams {\n /** Kernel size (odd number) */\n size: number\n}\n\n/**\n * Generate a triangle kernel (center-weighted)\n *\n * @example\n * ```ts\n * const kernel = triangleKernel({ size: 5 })\n * // weights: [1/9, 2/9, 3/9, 2/9, 1/9] (normalized)\n * ```\n */\nexport function triangleKernel(params: TriangleKernelParams): Kernel {\n const { size } = params\n\n // Force odd size\n const actualSize = size % 2 === 0 ? size + 1 : size\n const halfSize = Math.floor(actualSize / 2)\n\n const weights: number[] = []\n let sum = 0\n\n for (let i = 0; i < actualSize; i++) {\n // Maximum at center, decreasing toward edges\n const weight = halfSize + 1 - Math.abs(i - halfSize)\n weights.push(weight)\n sum += weight\n }\n\n // Normalize\n for (let i = 0; i < weights.length; i++) {\n weights[i] /= sum\n }\n\n return {\n type: 'triangle',\n weights,\n }\n}\n","import type { Point } from '../types'\n\nexport interface BilateralKernelParams {\n /**\n * Kernel size (odd number)\n * Recommended: 5-11\n */\n size: number\n /**\n * Spatial standard deviation\n * Weight decay based on index distance\n * Recommended: size / 3\n */\n sigmaSpace?: number\n /**\n * Value standard deviation\n * Weight decay based on coordinate value difference (edge preservation)\n * Lower = stronger edge preservation\n * Recommended: 5-30\n */\n sigmaValue: number\n}\n\nexport interface BilateralKernel {\n readonly type: 'bilateral'\n readonly size: number\n readonly sigmaSpace: number\n readonly sigmaValue: number\n /**\n * Dynamically compute weights for each point\n */\n computeWeights(center: Point, neighbors: Point[]): number[]\n}\n\n/**\n * Generate a bilateral kernel\n *\n * Unlike standard convolution, weights are determined considering\n * coordinate value similarity. This enables smoothing while preserving\n * edges (sharp direction changes, etc.).\n *\n * @example\n * ```ts\n * const kernel = bilateralKernel({\n * size: 7,\n * sigmaValue: 10\n * })\n * ```\n */\nexport function bilateralKernel(\n params: BilateralKernelParams\n): BilateralKernel {\n const { size, sigmaValue } = params\n const actualSize = size % 2 === 0 ? size + 1 : size\n const sigmaSpace = params.sigmaSpace ?? actualSize / 3\n\n const halfSize = Math.floor(actualSize / 2)\n\n // Pre-compute spatial weights\n const spatialWeights: number[] = []\n for (let i = 0; i < actualSize; i++) {\n const d = i - halfSize\n spatialWeights.push(Math.exp(-(d * d) / (2 * sigmaSpace * sigmaSpace)))\n }\n\n return {\n type: 'bilateral',\n size: actualSize,\n sigmaSpace,\n sigmaValue,\n\n computeWeights(center: Point, neighbors: Point[]): number[] {\n const weights: number[] = []\n let sum = 0\n\n for (let i = 0; i < neighbors.length; i++) {\n // Calculate coordinate value difference\n const dx = neighbors[i].x - center.x\n const dy = neighbors[i].y - center.y\n const valueDiff = dx * dx + dy * dy\n\n // Value-based weight\n const valueWeight = Math.exp(-valueDiff / (2 * sigmaValue * sigmaValue))\n\n // Spatial × value weight\n const weight = spatialWeights[i] * valueWeight\n weights.push(weight)\n sum += weight\n }\n\n // Normalize\n if (sum > 0) {\n for (let i = 0; i < weights.length; i++) {\n weights[i] /= sum\n }\n }\n\n return weights\n },\n }\n}\n"],"names":["FILTER_TYPE","b","a"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/kernels/types.ts","../src/smooth.ts","../src/StabilizedPointer.ts","../src/filters/NoiseFilter.ts","../src/filters/KalmanFilter.ts","../src/filters/MovingAverageFilter.ts","../src/filters/StringFilter.ts","../src/filters/EmaFilter.ts","../src/filters/OneEuroFilter.ts","../src/filters/LinearPredictionFilter.ts","../src/filters/DouglasPeuckerFilter.ts","../src/presets.ts","../src/kernels/gaussianKernel.ts","../src/kernels/boxKernel.ts","../src/kernels/triangleKernel.ts","../src/kernels/BilateralKernel.ts","../src/svg.ts","../src/prediction.ts","../src/spline.ts"],"sourcesContent":["import type { Point } from '../types'\n\n/**\n * Convolution kernel (fixed weights)\n */\nexport interface Kernel {\n readonly type: string\n readonly weights: number[]\n}\n\n/**\n * Adaptive kernel (dynamic weight computation)\n */\nexport interface AdaptiveKernel {\n readonly type: string\n readonly size: number\n computeWeights(center: Point, neighbors: Point[]): number[]\n}\n\n/**\n * All kernel types\n */\nexport type AnyKernel = Kernel | AdaptiveKernel\n\n/**\n * Check if kernel is adaptive\n */\nexport function isAdaptiveKernel(kernel: AnyKernel): kernel is AdaptiveKernel {\n return (\n 'computeWeights' in kernel && typeof kernel.computeWeights === 'function'\n )\n}\n\n/**\n * Padding mode\n * - 'reflect': Reflect at edges [3,2,1] | [1,2,3,4,5] | [5,4,3]\n * - 'edge': Repeat edge values [1,1,1] | [1,2,3,4,5] | [5,5,5]\n * - 'zero': Zero padding [0,0,0] | [1,2,3,4,5] | [0,0,0]\n */\nexport type PaddingMode = 'reflect' | 'edge' | 'zero'\n\nexport interface SmoothOptions {\n kernel: AnyKernel\n padding?: PaddingMode\n /** Preserve start and end points exactly (default: true) */\n preserveEndpoints?: boolean\n}\n","import type { Point } from './types'\nimport type { PaddingMode, SmoothOptions, Kernel } from './kernels/types'\nimport { isAdaptiveKernel } from './kernels/types'\n\n/**\n * Apply padding to extend point array\n */\nfunction applyPadding(\n points: Point[],\n halfSize: number,\n mode: PaddingMode\n): Point[] {\n if (points.length === 0) return []\n\n const padded: Point[] = []\n\n // Leading padding\n for (let i = halfSize; i > 0; i--) {\n switch (mode) {\n case 'reflect':\n padded.push(points[Math.min(i, points.length - 1)])\n break\n case 'edge':\n padded.push(points[0])\n break\n case 'zero':\n padded.push({ x: 0, y: 0 })\n break\n }\n }\n\n // Original data\n padded.push(...points)\n\n // Trailing padding\n for (let i = 1; i <= halfSize; i++) {\n switch (mode) {\n case 'reflect':\n padded.push(points[Math.max(0, points.length - 1 - i)])\n break\n case 'edge':\n padded.push(points[points.length - 1])\n break\n case 'zero':\n padded.push({ x: 0, y: 0 })\n break\n }\n }\n\n return padded\n}\n\n/**\n * Bidirectional convolution smoothing\n *\n * Supports both fixed-weight kernels (Gaussian, Box, Triangle) and\n * adaptive kernels (Bilateral).\n *\n * @example\n * ```ts\n * import { smooth, gaussianKernel, bilateralKernel } from '@stroke-stabilizer/core'\n *\n * // Standard convolution\n * const smoothed = smooth(points, {\n * kernel: gaussianKernel({ size: 7 }),\n * padding: 'reflect',\n * })\n *\n * // Bilateral (edge-preserving)\n * const edgePreserved = smooth(points, {\n * kernel: bilateralKernel({ size: 7, sigmaValue: 10 }),\n * padding: 'reflect',\n * })\n * ```\n */\nexport function smooth(points: Point[], options: SmoothOptions): Point[] {\n const { kernel, padding = 'reflect', preserveEndpoints = true } = options\n\n if (points.length === 0) return []\n\n // Save original endpoints if preserving\n const originalStart = points[0]\n const originalEnd = points[points.length - 1]\n\n let result: Point[]\n\n // Adaptive kernel (Bilateral, etc.)\n if (isAdaptiveKernel(kernel)) {\n const halfSize = Math.floor(kernel.size / 2)\n const padded = applyPadding(points, halfSize, padding)\n\n result = []\n\n for (let i = 0; i < points.length; i++) {\n const centerIdx = i + halfSize\n const center = padded[centerIdx]\n const neighbors: Point[] = []\n\n for (let k = 0; k < kernel.size; k++) {\n neighbors.push(padded[i + k])\n }\n\n const weights = kernel.computeWeights(center, neighbors)\n\n let sumX = 0\n let sumY = 0\n\n for (let k = 0; k < weights.length; k++) {\n sumX += neighbors[k].x * weights[k]\n sumY += neighbors[k].y * weights[k]\n }\n\n result.push({ x: sumX, y: sumY })\n }\n } else {\n // Fixed-weight kernel (Gaussian, Box, Triangle, etc.)\n const fixedKernel = kernel as Kernel\n const { weights } = fixedKernel\n\n if (weights.length <= 1) return [...points]\n\n const halfSize = Math.floor(weights.length / 2)\n const padded = applyPadding(points, halfSize, padding)\n\n result = []\n\n for (let i = 0; i < points.length; i++) {\n let sumX = 0\n let sumY = 0\n\n for (let k = 0; k < weights.length; k++) {\n const point = padded[i + k]\n sumX += point.x * weights[k]\n sumY += point.y * weights[k]\n }\n\n result.push({ x: sumX, y: sumY })\n }\n }\n\n // Restore original endpoints if preserving\n if (preserveEndpoints && result.length > 0) {\n result[0] = { ...originalStart }\n if (result.length > 1) {\n result[result.length - 1] = { ...originalEnd }\n }\n }\n\n return result\n}\n","import type { Filter, Point, PointerPoint, UpdatableFilter } from './types'\nimport type { Kernel, PaddingMode } from './kernels/types'\nimport { smooth } from './smooth'\n\n/**\n * Post-processor configuration\n */\ninterface PostProcessor {\n kernel: Kernel\n padding: PaddingMode\n}\n\n/**\n * Batch processing configuration\n */\nexport interface BatchConfig {\n /** Callback when a batch of points is processed */\n onBatch?: (points: PointerPoint[]) => void\n /** Callback for each processed point */\n onPoint?: (point: PointerPoint) => void\n}\n\n/**\n * StabilizedPointer configuration options\n */\nexport interface StabilizedPointerOptions {\n /**\n * Whether to append the raw endpoint when finishing a stroke.\n * This ensures the stabilized stroke ends at the actual input point.\n * @default true\n */\n appendEndpoint?: boolean\n}\n\n/**\n * Mutable Pipeline Pattern implementation\n *\n * A pipeline that allows adding, removing, and updating filters at runtime.\n * Always ready to execute without requiring a .build() call.\n *\n * @example\n * ```ts\n * const pointer = new StabilizedPointer()\n * // Real-time layer\n * .addFilter(noiseFilter({ minDistance: 2 }))\n * .addFilter(kalmanFilter({ processNoise: 0.1 }))\n * // Post-processing layer\n * .addPostProcess(gaussianKernel({ size: 7 }))\n *\n * // During drawing\n * pointer.process(point)\n *\n * // On completion\n * const finalStroke = pointer.finish()\n * ```\n */\nexport class StabilizedPointer {\n private filters: Filter[] = []\n private postProcessors: PostProcessor[] = []\n private buffer: PointerPoint[] = []\n private lastRawPoint: PointerPoint | null = null\n\n // Configuration options\n private options: Required<StabilizedPointerOptions>\n\n // Batch processing fields\n private batchConfig: BatchConfig | null = null\n private pendingPoints: PointerPoint[] = []\n private rafId: number | null = null\n\n /**\n * Create a new StabilizedPointer\n * @param options Configuration options\n */\n constructor(options: StabilizedPointerOptions = {}) {\n this.options = {\n appendEndpoint: options.appendEndpoint ?? true,\n }\n }\n\n /**\n * Add a filter to the pipeline\n * @returns this (for method chaining)\n */\n addFilter(filter: Filter): this {\n this.filters.push(filter)\n return this\n }\n\n /**\n * Remove a filter by type\n * @returns true if removed, false if not found\n */\n removeFilter(type: string): boolean {\n const index = this.filters.findIndex((f) => f.type === type)\n if (index === -1) return false\n this.filters.splice(index, 1)\n return true\n }\n\n /**\n * Update parameters of a filter by type\n * @returns true if updated, false if not found\n */\n updateFilter<TParams>(type: string, params: Partial<TParams>): boolean {\n const filter = this.filters.find((f) => f.type === type)\n if (!filter) return false\n\n if (this.isUpdatableFilter<TParams>(filter)) {\n filter.updateParams(params)\n return true\n }\n return false\n }\n\n /**\n * Get a filter by type\n */\n getFilter(type: string): Filter | undefined {\n return this.filters.find((f) => f.type === type)\n }\n\n /**\n * Get list of current filter types\n */\n getFilterTypes(): string[] {\n return this.filters.map((f) => f.type)\n }\n\n /**\n * Check if a filter exists\n */\n hasFilter(type: string): boolean {\n return this.filters.some((f) => f.type === type)\n }\n\n /**\n * Process a point through the pipeline\n * @returns Processed result (null if rejected by a filter)\n */\n process(point: PointerPoint): PointerPoint | null {\n // Save raw input for convergence on finish\n this.lastRawPoint = point\n\n let current: PointerPoint | null = point\n\n for (const filter of this.filters) {\n if (current === null) break\n current = filter.process(current)\n }\n\n if (current !== null) {\n this.buffer.push(current)\n }\n\n return current\n }\n\n /**\n * Process multiple points at once\n * @returns Array of processed results (nulls excluded)\n */\n processAll(points: PointerPoint[]): PointerPoint[] {\n const results: PointerPoint[] = []\n for (const point of points) {\n const result = this.process(point)\n if (result !== null) {\n results.push(result)\n }\n }\n return results\n }\n\n /**\n * Get the processed buffer\n */\n getBuffer(): readonly PointerPoint[] {\n return this.buffer\n }\n\n /**\n * Clear and return the processed buffer\n */\n flushBuffer(): PointerPoint[] {\n const flushed = [...this.buffer]\n this.buffer = []\n return flushed\n }\n\n /**\n * Reset all filters and clear the buffer\n *\n * Call this to prepare for a new stroke without destroying the pipeline configuration.\n * Filters are reset to their initial state and the buffer is cleared.\n *\n * @example\n * ```ts\n * // After finishing a stroke\n * const result = pointer.finish() // automatically calls reset()\n *\n * // Or manually reset without finishing\n * pointer.reset()\n * ```\n */\n reset(): void {\n for (const filter of this.filters) {\n filter.reset()\n }\n this.buffer = []\n this.lastRawPoint = null\n this.hasDrained = false\n }\n\n /**\n * Clear the pipeline (remove all filters)\n */\n clear(): void {\n this.filters = []\n this.postProcessors = []\n this.buffer = []\n }\n\n /**\n * Get number of filters\n */\n get length(): number {\n return this.filters.length\n }\n\n // ========================================\n // Post-processing layer\n // ========================================\n\n /**\n * Add post-processor to the pipeline\n * @returns this (for method chaining)\n */\n addPostProcess(\n kernel: Kernel,\n options: { padding?: PaddingMode } = {}\n ): this {\n this.postProcessors.push({\n kernel,\n padding: options.padding ?? 'reflect',\n })\n return this\n }\n\n /**\n * Remove a post-processor by type\n * @returns true if removed, false if not found\n */\n removePostProcess(type: string): boolean {\n const index = this.postProcessors.findIndex((p) => p.kernel.type === type)\n if (index === -1) return false\n this.postProcessors.splice(index, 1)\n return true\n }\n\n /**\n * Check if a post-processor exists\n */\n hasPostProcess(type: string): boolean {\n return this.postProcessors.some((p) => p.kernel.type === type)\n }\n\n /**\n * Get list of post-processor types\n */\n getPostProcessTypes(): string[] {\n return this.postProcessors.map((p) => p.kernel.type)\n }\n\n /**\n * Get number of post-processors\n */\n get postProcessLength(): number {\n return this.postProcessors.length\n }\n\n /**\n * Finish the stroke and return post-processed results, without resetting\n *\n * Use this when you want to get the final result but keep the buffer intact.\n * Useful for previewing post-processing results or comparing different settings.\n *\n * @example\n * ```ts\n * pointer.addPostProcess(gaussianKernel({ size: 5 }))\n * const preview1 = pointer.finishWithoutReset()\n *\n * // Change settings and re-apply\n * pointer.removePostProcess('gaussian')\n * pointer.addPostProcess(bilateralKernel({ size: 7, sigmaValue: 10 }))\n * const preview2 = pointer.finishWithoutReset()\n *\n * // Finalize when done\n * const final = pointer.finish()\n * ```\n */\n finishWithoutReset(): Point[] {\n // Flush any pending batched points first\n if (this.batchConfig) {\n this.flushBatch()\n }\n\n // Append endpoint to ensure stroke ends at actual input point\n this.appendEndpoint()\n\n let points: Point[] = [...this.buffer]\n\n // Apply post-processors in order\n for (const processor of this.postProcessors) {\n points = smooth(points, {\n kernel: processor.kernel,\n padding: processor.padding,\n })\n }\n\n return points\n }\n\n /**\n * Track if drain has been called to avoid duplicate draining\n */\n private hasDrained = false\n\n /**\n * Append the final raw input point to ensure stroke ends at the actual endpoint\n *\n * Instead of draining filters (which adds many extra points),\n * we simply append the raw endpoint. The post-processing phase\n * with bidirectional convolution will naturally smooth the transition.\n */\n private appendEndpoint(): void {\n // Skip if disabled via options\n if (!this.options.appendEndpoint) {\n return\n }\n // Avoid duplicate appending (finishWithoutReset may be called multiple times)\n if (this.hasDrained) {\n return\n }\n if (this.lastRawPoint === null || this.buffer.length === 0) {\n return\n }\n\n this.hasDrained = true\n\n const target = this.lastRawPoint\n const lastBufferPoint = this.buffer[this.buffer.length - 1]\n\n // Calculate distance between last stabilized point and target\n const dx = target.x - lastBufferPoint.x\n const dy = target.y - lastBufferPoint.y\n const distance = Math.sqrt(dx * dx + dy * dy)\n\n // If already close enough, no need to append\n if (distance < 1) {\n return\n }\n\n // Append the raw endpoint directly to the buffer\n // Post-processing (Gaussian, etc.) will smooth the transition\n this.buffer.push({\n x: target.x,\n y: target.y,\n pressure: target.pressure ?? 1,\n timestamp: target.timestamp + 8,\n })\n }\n\n /**\n * Finish the stroke and return post-processed results\n *\n * This applies all post-processors to the buffer, then resets filters and clears the buffer.\n * Use this at the end of a stroke to get the final smoothed result.\n *\n * @example\n * ```ts\n * // During drawing\n * pointer.process(point)\n *\n * // On stroke end\n * const finalStroke = pointer.finish()\n * ```\n */\n finish(): Point[] {\n const points = this.finishWithoutReset()\n this.reset()\n return points\n }\n\n // ========================================\n // Batch processing layer (rAF)\n // ========================================\n\n /**\n * Enable requestAnimationFrame batch processing\n *\n * When enabled, points queued via queue() are batched and processed\n * on the next animation frame, reducing CPU load for high-frequency\n * pointer events.\n *\n * @returns this (for method chaining)\n *\n * @example\n * ```ts\n * const pointer = new StabilizedPointer()\n * .addFilter(noiseFilter({ minDistance: 2 }))\n * .enableBatching({\n * onBatch: (points) => drawPoints(points),\n * onPoint: (point) => updatePreview(point)\n * })\n *\n * canvas.onpointermove = (e) => {\n * pointer.queue({ x: e.clientX, y: e.clientY, timestamp: e.timeStamp })\n * }\n * ```\n */\n enableBatching(config: BatchConfig = {}): this {\n this.batchConfig = config\n return this\n }\n\n /**\n * Disable batch processing\n * Flushes any pending points before disabling\n * @returns this (for method chaining)\n */\n disableBatching(): this {\n this.flushBatch()\n this.batchConfig = null\n return this\n }\n\n /**\n * Check if batch processing is enabled\n */\n get isBatchingEnabled(): boolean {\n return this.batchConfig !== null\n }\n\n /**\n * Queue a point for batch processing\n *\n * If batching is enabled, the point is queued and processed on the next\n * animation frame. If batching is disabled, the point is processed immediately.\n *\n * @returns this (for method chaining, useful for queueing multiple points)\n */\n queue(point: PointerPoint): this {\n if (this.batchConfig) {\n this.pendingPoints.push(point)\n this.scheduleFlush()\n } else {\n // Fallback to immediate processing\n this.process(point)\n }\n return this\n }\n\n /**\n * Queue multiple points for batch processing\n * @returns this (for method chaining)\n */\n queueAll(points: PointerPoint[]): this {\n if (this.batchConfig) {\n this.pendingPoints.push(...points)\n this.scheduleFlush()\n } else {\n this.processAll(points)\n }\n return this\n }\n\n /**\n * Force flush pending batched points immediately\n * @returns Array of processed points\n */\n flushBatch(): PointerPoint[] {\n this.cancelScheduledFlush()\n return this.processPendingPoints()\n }\n\n /**\n * Get number of pending points in the batch queue\n */\n get pendingCount(): number {\n return this.pendingPoints.length\n }\n\n // ----------------------------------------\n // Private batch processing methods\n // ----------------------------------------\n\n private scheduleFlush(): void {\n if (this.rafId !== null) return\n\n // Use rAF if available, otherwise use setTimeout as fallback\n if (typeof requestAnimationFrame !== 'undefined') {\n this.rafId = requestAnimationFrame(() => {\n this.rafId = null\n this.processPendingPoints()\n })\n } else {\n // Node.js or non-browser environment fallback\n this.rafId = setTimeout(() => {\n this.rafId = null\n this.processPendingPoints()\n }, 16) as unknown as number\n }\n }\n\n private cancelScheduledFlush(): void {\n if (this.rafId === null) return\n\n if (typeof cancelAnimationFrame !== 'undefined') {\n cancelAnimationFrame(this.rafId)\n } else {\n clearTimeout(this.rafId)\n }\n this.rafId = null\n }\n\n private processPendingPoints(): PointerPoint[] {\n if (this.pendingPoints.length === 0) return []\n\n const points = this.pendingPoints\n this.pendingPoints = []\n\n const results: PointerPoint[] = []\n\n for (const point of points) {\n const result = this.process(point)\n if (result !== null) {\n results.push(result)\n // Call onPoint callback for each processed point\n this.batchConfig?.onPoint?.(result)\n }\n }\n\n // Call onBatch callback with all processed points\n if (results.length > 0) {\n this.batchConfig?.onBatch?.(results)\n }\n\n return results\n }\n\n private isUpdatableFilter<TParams>(\n filter: Filter\n ): filter is UpdatableFilter<TParams> {\n return 'updateParams' in filter && typeof filter.updateParams === 'function'\n }\n}\n","import type { Filter, PointerPoint, UpdatableFilter } from '../types'\n\nexport interface NoiseFilterParams {\n /** Minimum movement distance (px). Movements smaller than this are rejected */\n minDistance: number\n}\n\nconst FILTER_TYPE = 'noise' as const\n\n/**\n * Noise filter\n *\n * Rejects points to eliminate jitter if the distance from the\n * previous point is less than minDistance.\n */\nclass NoiseFilterImpl implements UpdatableFilter<NoiseFilterParams> {\n readonly type = FILTER_TYPE\n private params: NoiseFilterParams\n private lastPoint: PointerPoint | null = null\n\n constructor(params: NoiseFilterParams) {\n this.params = { ...params }\n }\n\n process(point: PointerPoint): PointerPoint | null {\n if (this.lastPoint === null) {\n this.lastPoint = point\n return point\n }\n\n const dx = point.x - this.lastPoint.x\n const dy = point.y - this.lastPoint.y\n const distance = Math.sqrt(dx * dx + dy * dy)\n\n if (distance < this.params.minDistance) {\n return null // Reject\n }\n\n this.lastPoint = point\n return point\n }\n\n updateParams(params: Partial<NoiseFilterParams>): void {\n this.params = { ...this.params, ...params }\n }\n\n reset(): void {\n this.lastPoint = null\n }\n}\n\n/**\n * Create a noise filter\n *\n * @example\n * ```ts\n * const pointer = new StabilizedPointer()\n * .addFilter(noiseFilter({ minDistance: 2 }))\n * ```\n */\nexport function noiseFilter(params: NoiseFilterParams): Filter {\n return new NoiseFilterImpl(params)\n}\n","import type { Filter, PointerPoint, UpdatableFilter } from '../types'\n\nexport interface KalmanFilterParams {\n /** Process noise (Q): Lower values trust prediction more */\n processNoise: number\n /** Measurement noise (R): Higher values result in stronger smoothing */\n measurementNoise: number\n}\n\ninterface KalmanState {\n x: number\n y: number\n p: number // covariance\n lastTimestamp: number\n}\n\nconst FILTER_TYPE = 'kalman' as const\n\n/**\n * Kalman filter\n *\n * Simple position-only Kalman filter for smooth cursor tracking.\n * Uses prediction from previous position (no velocity) to avoid runaway behavior.\n */\nclass KalmanFilterImpl implements UpdatableFilter<KalmanFilterParams> {\n readonly type = FILTER_TYPE\n private params: KalmanFilterParams\n private state: KalmanState | null = null\n\n constructor(params: KalmanFilterParams) {\n this.params = { ...params }\n }\n\n process(point: PointerPoint): PointerPoint | null {\n if (this.state === null) {\n this.state = {\n x: point.x,\n y: point.y,\n p: 1,\n lastTimestamp: point.timestamp,\n }\n return point\n }\n\n const { processNoise: Q, measurementNoise: R } = this.params\n\n // Prediction step: assume position stays the same (no velocity model)\n const predictedX = this.state.x\n const predictedY = this.state.y\n const predictedP = this.state.p + Q\n\n // Update step (calculate Kalman gain)\n const K = predictedP / (predictedP + R)\n\n // State update: blend prediction with measurement\n const newX = predictedX + K * (point.x - predictedX)\n const newY = predictedY + K * (point.y - predictedY)\n const newP = (1 - K) * predictedP\n\n this.state = {\n x: newX,\n y: newY,\n p: newP,\n lastTimestamp: point.timestamp,\n }\n\n return {\n x: newX,\n y: newY,\n pressure: point.pressure,\n tiltX: point.tiltX,\n tiltY: point.tiltY,\n timestamp: point.timestamp,\n }\n }\n\n updateParams(params: Partial<KalmanFilterParams>): void {\n this.params = { ...this.params, ...params }\n }\n\n reset(): void {\n this.state = null\n }\n}\n\n/**\n * Create a Kalman filter\n *\n * @example\n * ```ts\n * const pointer = new StabilizedPointer()\n * .addFilter(kalmanFilter({\n * processNoise: 0.1,\n * measurementNoise: 0.5\n * }))\n * ```\n */\nexport function kalmanFilter(params: KalmanFilterParams): Filter {\n return new KalmanFilterImpl(params)\n}\n","import type { Filter, PointerPoint, UpdatableFilter } from '../types'\n\nexport interface MovingAverageFilterParams {\n /** Number of points to average */\n windowSize: number\n}\n\nconst FILTER_TYPE = 'movingAverage' as const\n\n/**\n * Moving average filter\n *\n * Smooths by averaging the last N points.\n * Simpler and faster than Gaussian filter.\n */\nclass MovingAverageFilterImpl implements UpdatableFilter<MovingAverageFilterParams> {\n readonly type = FILTER_TYPE\n private params: MovingAverageFilterParams\n private window: PointerPoint[] = []\n\n constructor(params: MovingAverageFilterParams) {\n this.params = { ...params }\n }\n\n process(point: PointerPoint): PointerPoint | null {\n this.window.push(point)\n\n // Remove old points when exceeding window size\n while (this.window.length > this.params.windowSize) {\n this.window.shift()\n }\n\n // Calculate average\n let sumX = 0\n let sumY = 0\n let sumPressure = 0\n let pressureCount = 0\n let sumTiltX = 0\n let tiltXCount = 0\n let sumTiltY = 0\n let tiltYCount = 0\n\n for (const p of this.window) {\n sumX += p.x\n sumY += p.y\n if (p.pressure !== undefined) {\n sumPressure += p.pressure\n pressureCount++\n }\n if (p.tiltX !== undefined) {\n sumTiltX += p.tiltX\n tiltXCount++\n }\n if (p.tiltY !== undefined) {\n sumTiltY += p.tiltY\n tiltYCount++\n }\n }\n\n const avgX = sumX / this.window.length\n const avgY = sumY / this.window.length\n const avgPressure =\n pressureCount > 0 ? sumPressure / pressureCount : undefined\n const avgTiltX = tiltXCount > 0 ? sumTiltX / tiltXCount : undefined\n const avgTiltY = tiltYCount > 0 ? sumTiltY / tiltYCount : undefined\n\n return {\n x: avgX,\n y: avgY,\n pressure: avgPressure,\n tiltX: avgTiltX,\n tiltY: avgTiltY,\n timestamp: point.timestamp,\n }\n }\n\n updateParams(params: Partial<MovingAverageFilterParams>): void {\n this.params = { ...this.params, ...params }\n // Remove old points if window size decreased\n while (this.window.length > this.params.windowSize) {\n this.window.shift()\n }\n }\n\n reset(): void {\n this.window = []\n }\n}\n\n/**\n * Create a moving average filter\n *\n * @example\n * ```ts\n * const pointer = new StabilizedPointer()\n * .addFilter(movingAverageFilter({ windowSize: 5 }))\n * ```\n */\nexport function movingAverageFilter(params: MovingAverageFilterParams): Filter {\n return new MovingAverageFilterImpl(params)\n}\n","import type { Filter, PointerPoint, UpdatableFilter } from '../types'\n\nexport interface StringFilterParams {\n /** String length (px): Dead zone radius */\n stringLength: number\n}\n\nconst FILTER_TYPE = 'string' as const\n\n/**\n * String stabilization filter (Lazy Brush / String Stabilization)\n *\n * Behaves as if there's a virtual \"string\" between the pen tip and drawing point.\n * Drawing point doesn't move within the string length, but gets pulled when exceeded.\n */\nclass StringFilterImpl implements UpdatableFilter<StringFilterParams> {\n readonly type = FILTER_TYPE\n private params: StringFilterParams\n private anchorPoint: PointerPoint | null = null\n\n constructor(params: StringFilterParams) {\n this.params = { ...params }\n }\n\n process(point: PointerPoint): PointerPoint | null {\n if (this.anchorPoint === null) {\n this.anchorPoint = point\n return point\n }\n\n const dx = point.x - this.anchorPoint.x\n const dy = point.y - this.anchorPoint.y\n const distance = Math.sqrt(dx * dx + dy * dy)\n\n // Within string length, return anchor point (no movement)\n if (distance <= this.params.stringLength) {\n return {\n ...this.anchorPoint,\n pressure: point.pressure,\n tiltX: point.tiltX,\n tiltY: point.tiltY,\n timestamp: point.timestamp,\n }\n }\n\n // Move by the amount exceeding string length\n const ratio = (distance - this.params.stringLength) / distance\n const newX = this.anchorPoint.x + dx * ratio\n const newY = this.anchorPoint.y + dy * ratio\n\n this.anchorPoint = {\n x: newX,\n y: newY,\n pressure: point.pressure,\n tiltX: point.tiltX,\n tiltY: point.tiltY,\n timestamp: point.timestamp,\n }\n\n return this.anchorPoint\n }\n\n updateParams(params: Partial<StringFilterParams>): void {\n this.params = { ...this.params, ...params }\n }\n\n reset(): void {\n this.anchorPoint = null\n }\n}\n\n/**\n * Create a string stabilization filter\n *\n * @example\n * ```ts\n * const pointer = new StabilizedPointer()\n * .addFilter(stringFilter({ stringLength: 10 }))\n * ```\n */\nexport function stringFilter(params: StringFilterParams): Filter {\n return new StringFilterImpl(params)\n}\n","import type { Filter, PointerPoint, UpdatableFilter } from '../types'\n\nexport interface EmaFilterParams {\n /**\n * Smoothing coefficient (0-1)\n * - Lower value: stronger smoothing (emphasizes past values)\n * - Higher value: more responsive (emphasizes new values)\n */\n alpha: number\n}\n\nconst FILTER_TYPE = 'ema' as const\n\n/**\n * Exponential Moving Average (EMA) filter\n *\n * IIR filter. Weights newer values more heavily, older values decay exponentially.\n * Lowest computational cost and minimal latency.\n *\n * Formula: y[n] = α * x[n] + (1 - α) * y[n-1]\n */\nclass EmaFilterImpl implements UpdatableFilter<EmaFilterParams> {\n readonly type = FILTER_TYPE\n private params: EmaFilterParams\n private lastPoint: PointerPoint | null = null\n\n constructor(params: EmaFilterParams) {\n this.params = { ...params }\n }\n\n process(point: PointerPoint): PointerPoint | null {\n if (this.lastPoint === null) {\n this.lastPoint = point\n return point\n }\n\n const { alpha } = this.params\n\n // EMA: y = α * x + (1 - α) * y_prev\n const newX = alpha * point.x + (1 - alpha) * this.lastPoint.x\n const newY = alpha * point.y + (1 - alpha) * this.lastPoint.y\n\n // Apply EMA to pressure if present\n let newPressure: number | undefined\n if (point.pressure !== undefined && this.lastPoint.pressure !== undefined) {\n newPressure =\n alpha * point.pressure + (1 - alpha) * this.lastPoint.pressure\n } else {\n newPressure = point.pressure\n }\n\n // Apply EMA to tiltX if present\n let newTiltX: number | undefined\n if (point.tiltX !== undefined && this.lastPoint.tiltX !== undefined) {\n newTiltX = alpha * point.tiltX + (1 - alpha) * this.lastPoint.tiltX\n } else {\n newTiltX = point.tiltX\n }\n\n // Apply EMA to tiltY if present\n let newTiltY: number | undefined\n if (point.tiltY !== undefined && this.lastPoint.tiltY !== undefined) {\n newTiltY = alpha * point.tiltY + (1 - alpha) * this.lastPoint.tiltY\n } else {\n newTiltY = point.tiltY\n }\n\n this.lastPoint = {\n x: newX,\n y: newY,\n pressure: newPressure,\n tiltX: newTiltX,\n tiltY: newTiltY,\n timestamp: point.timestamp,\n }\n\n return this.lastPoint\n }\n\n updateParams(params: Partial<EmaFilterParams>): void {\n this.params = { ...this.params, ...params }\n }\n\n reset(): void {\n this.lastPoint = null\n }\n}\n\n/**\n * Create an Exponential Moving Average (EMA) filter\n *\n * @example\n * ```ts\n * // Strong smoothing\n * const pointer = new StabilizedPointer()\n * .addFilter(emaFilter({ alpha: 0.2 }))\n *\n * // Light smoothing\n * const pointer = new StabilizedPointer()\n * .addFilter(emaFilter({ alpha: 0.7 }))\n * ```\n */\nexport function emaFilter(params: EmaFilterParams): Filter {\n return new EmaFilterImpl(params)\n}\n","import type { Filter, PointerPoint, UpdatableFilter } from '../types'\n\nexport interface OneEuroFilterParams {\n /**\n * Minimum cutoff frequency (Hz)\n * Smoothing strength at low speed. Lower = smoother.\n * Recommended: 0.5 - 2.0\n */\n minCutoff: number\n /**\n * Speed coefficient\n * Rate of cutoff frequency increase based on speed.\n * Higher = more responsive at high speed.\n * Recommended: 0.001 - 0.01\n */\n beta: number\n /**\n * Derivative cutoff frequency (Hz)\n * Smoothing for velocity estimation. Usually fixed at 1.0.\n */\n dCutoff?: number\n}\n\nconst FILTER_TYPE = 'oneEuro' as const\n\n/**\n * Low-pass filter (internal use)\n */\nclass LowPassFilter {\n private y: number | null = null\n private alpha: number = 1\n\n setAlpha(alpha: number): void {\n this.alpha = Math.max(0, Math.min(1, alpha))\n }\n\n filter(value: number): number {\n if (this.y === null) {\n this.y = value\n } else {\n this.y = this.alpha * value + (1 - this.alpha) * this.y\n }\n return this.y\n }\n\n reset(): void {\n this.y = null\n }\n\n lastValue(): number | null {\n return this.y\n }\n}\n\n/**\n * One Euro Filter\n *\n * Speed-adaptive low-pass filter.\n * - At low speed: Strong smoothing (jitter removal)\n * - At high speed: Light smoothing (reduce latency)\n *\n * Paper: https://cristal.univ-lille.fr/~casiez/1euro/\n */\nclass OneEuroFilterImpl implements UpdatableFilter<OneEuroFilterParams> {\n readonly type = FILTER_TYPE\n private params: OneEuroFilterParams\n\n private xFilter = new LowPassFilter()\n private yFilter = new LowPassFilter()\n private dxFilter = new LowPassFilter()\n private dyFilter = new LowPassFilter()\n private pressureFilter = new LowPassFilter()\n private tiltXFilter = new LowPassFilter()\n private tiltYFilter = new LowPassFilter()\n\n private lastTimestamp: number | null = null\n\n constructor(params: OneEuroFilterParams) {\n this.params = {\n dCutoff: 1.0,\n ...params,\n }\n }\n\n process(point: PointerPoint): PointerPoint | null {\n // Calculate sampling rate\n let rate = 60 // Default 60Hz\n if (this.lastTimestamp !== null) {\n const dt = (point.timestamp - this.lastTimestamp) / 1000\n if (dt > 0) {\n rate = 1 / dt\n }\n }\n this.lastTimestamp = point.timestamp\n\n const { minCutoff, beta, dCutoff } = this.params\n\n // Process X axis\n const newX = this.filterAxis(\n point.x,\n this.xFilter,\n this.dxFilter,\n rate,\n minCutoff,\n beta,\n dCutoff!\n )\n\n // Process Y axis\n const newY = this.filterAxis(\n point.y,\n this.yFilter,\n this.dyFilter,\n rate,\n minCutoff,\n beta,\n dCutoff!\n )\n\n // Process pressure (if present)\n let newPressure: number | undefined\n if (point.pressure !== undefined) {\n // Pressure uses simple EMA without speed adaptation\n const alpha = this.computeAlpha(minCutoff, rate)\n this.pressureFilter.setAlpha(alpha)\n newPressure = this.pressureFilter.filter(point.pressure)\n }\n\n // Process tiltX (if present)\n let newTiltX: number | undefined\n if (point.tiltX !== undefined) {\n const alpha = this.computeAlpha(minCutoff, rate)\n this.tiltXFilter.setAlpha(alpha)\n newTiltX = this.tiltXFilter.filter(point.tiltX)\n }\n\n // Process tiltY (if present)\n let newTiltY: number | undefined\n if (point.tiltY !== undefined) {\n const alpha = this.computeAlpha(minCutoff, rate)\n this.tiltYFilter.setAlpha(alpha)\n newTiltY = this.tiltYFilter.filter(point.tiltY)\n }\n\n return {\n x: newX,\n y: newY,\n pressure: newPressure,\n tiltX: newTiltX,\n tiltY: newTiltY,\n timestamp: point.timestamp,\n }\n }\n\n private filterAxis(\n value: number,\n valueFilter: LowPassFilter,\n derivFilter: LowPassFilter,\n rate: number,\n minCutoff: number,\n beta: number,\n dCutoff: number\n ): number {\n // Get previous value\n const prevValue = valueFilter.lastValue()\n\n // Calculate derivative (velocity)\n let dValue = 0\n if (prevValue !== null) {\n dValue = (value - prevValue) * rate\n }\n\n // Filter the derivative\n const dAlpha = this.computeAlpha(dCutoff, rate)\n derivFilter.setAlpha(dAlpha)\n const filteredDValue = derivFilter.filter(dValue)\n\n // Adjust cutoff frequency based on speed\n const cutoff = minCutoff + beta * Math.abs(filteredDValue)\n\n // Filter the value\n const alpha = this.computeAlpha(cutoff, rate)\n valueFilter.setAlpha(alpha)\n return valueFilter.filter(value)\n }\n\n private computeAlpha(cutoff: number, rate: number): number {\n const tau = 1 / (2 * Math.PI * cutoff)\n const te = 1 / rate\n return 1 / (1 + tau / te)\n }\n\n updateParams(params: Partial<OneEuroFilterParams>): void {\n this.params = { ...this.params, ...params }\n }\n\n reset(): void {\n this.xFilter.reset()\n this.yFilter.reset()\n this.dxFilter.reset()\n this.dyFilter.reset()\n this.pressureFilter.reset()\n this.tiltXFilter.reset()\n this.tiltYFilter.reset()\n this.lastTimestamp = null\n }\n}\n\n/**\n * Create a One Euro Filter\n *\n * Speed-adaptive low-pass filter.\n * Strong smoothing at low speed, responsive at high speed.\n *\n * @example\n * ```ts\n * // Balanced settings\n * const pointer = new StabilizedPointer()\n * .addFilter(oneEuroFilter({\n * minCutoff: 1.0,\n * beta: 0.007\n * }))\n *\n * // Smoother (higher latency)\n * const smooth = oneEuroFilter({ minCutoff: 0.5, beta: 0.001 })\n *\n * // More responsive (may have jitter)\n * const responsive = oneEuroFilter({ minCutoff: 2.0, beta: 0.01 })\n * ```\n */\nexport function oneEuroFilter(params: OneEuroFilterParams): Filter {\n return new OneEuroFilterImpl(params)\n}\n","import type { Filter, PointerPoint, UpdatableFilter } from '../types'\n\nexport interface LinearPredictionFilterParams {\n /**\n * Number of points used for prediction\n * More points = more stable but higher computation cost\n * Recommended: 3-5\n */\n historySize: number\n /**\n * Prediction strength (0-1)\n * - 0: No prediction (returns current position as-is)\n * - 1: Full prediction (maximally considers velocity/acceleration)\n * Recommended: 0.3-0.7\n */\n predictionFactor: number\n /**\n * Smoothing coefficient (0-1)\n * EMA coefficient applied to prediction output\n * Recommended: 0.5-0.8\n */\n smoothing?: number\n}\n\nconst FILTER_TYPE = 'linearPrediction' as const\n\n/**\n * Linear prediction filter\n *\n * Compensates for filter-induced latency by calculating velocity and\n * acceleration from past positions to predict the next position.\n *\n * Method:\n * 1. Estimate velocity and acceleration from past N points using least squares\n * 2. Predicted position = current position + velocity * Δt + 0.5 * acceleration * Δt²\n * 3. Blend current position and predicted position using prediction factor\n */\nclass LinearPredictionFilterImpl implements UpdatableFilter<LinearPredictionFilterParams> {\n readonly type = FILTER_TYPE\n private params: LinearPredictionFilterParams\n private history: PointerPoint[] = []\n private lastOutput: PointerPoint | null = null\n\n constructor(params: LinearPredictionFilterParams) {\n this.params = {\n smoothing: 0.6,\n ...params,\n }\n }\n\n process(point: PointerPoint): PointerPoint | null {\n this.history.push(point)\n\n // Remove old entries when history exceeds historySize+1\n while (this.history.length > this.params.historySize + 1) {\n this.history.shift()\n }\n\n // Return first point as-is\n if (this.history.length === 1) {\n this.lastOutput = point\n return point\n }\n\n // Estimate velocity and acceleration\n const { velocity, acceleration } = this.estimateMotion()\n\n // Calculate time delta (in seconds)\n const dt =\n this.history.length >= 2\n ? (this.history[this.history.length - 1].timestamp -\n this.history[this.history.length - 2].timestamp) /\n 1000\n : 1 / 60 // Default 60fps\n\n // Calculate predicted position\n const { predictionFactor } = this.params\n const predictedX =\n point.x +\n velocity.x * dt * predictionFactor +\n 0.5 * acceleration.x * dt * dt * predictionFactor\n const predictedY =\n point.y +\n velocity.y * dt * predictionFactor +\n 0.5 * acceleration.y * dt * dt * predictionFactor\n\n // Apply smoothing\n let outputX = predictedX\n let outputY = predictedY\n let outputPressure = point.pressure\n let outputTiltX = point.tiltX\n let outputTiltY = point.tiltY\n\n if (this.lastOutput !== null && this.params.smoothing !== undefined) {\n const s = this.params.smoothing\n outputX = s * predictedX + (1 - s) * this.lastOutput.x\n outputY = s * predictedY + (1 - s) * this.lastOutput.y\n\n if (\n point.pressure !== undefined &&\n this.lastOutput.pressure !== undefined\n ) {\n outputPressure = s * point.pressure + (1 - s) * this.lastOutput.pressure\n }\n\n if (point.tiltX !== undefined && this.lastOutput.tiltX !== undefined) {\n outputTiltX = s * point.tiltX + (1 - s) * this.lastOutput.tiltX\n }\n\n if (point.tiltY !== undefined && this.lastOutput.tiltY !== undefined) {\n outputTiltY = s * point.tiltY + (1 - s) * this.lastOutput.tiltY\n }\n }\n\n this.lastOutput = {\n x: outputX,\n y: outputY,\n pressure: outputPressure,\n tiltX: outputTiltX,\n tiltY: outputTiltY,\n timestamp: point.timestamp,\n }\n\n return this.lastOutput\n }\n\n /**\n * Estimate velocity and acceleration using least squares\n */\n private estimateMotion(): {\n velocity: { x: number; y: number }\n acceleration: { x: number; y: number }\n } {\n const n = this.history.length\n if (n < 2) {\n return {\n velocity: { x: 0, y: 0 },\n acceleration: { x: 0, y: 0 },\n }\n }\n\n // Normalize time (first point = 0)\n const t0 = this.history[0].timestamp\n const times = this.history.map((p) => (p.timestamp - t0) / 1000)\n const xs = this.history.map((p) => p.x)\n const ys = this.history.map((p) => p.y)\n\n if (n === 2) {\n // Simple velocity calculation for 2 points\n const dt = times[1] - times[0]\n if (dt <= 0) {\n return { velocity: { x: 0, y: 0 }, acceleration: { x: 0, y: 0 } }\n }\n return {\n velocity: {\n x: (xs[1] - xs[0]) / dt,\n y: (ys[1] - ys[0]) / dt,\n },\n acceleration: { x: 0, y: 0 },\n }\n }\n\n // For 3+ points, use quadratic polynomial fitting\n // x(t) = a + b*t + c*t^2\n // velocity = b + 2*c*t\n // acceleration = 2*c\n\n const fitX = this.polynomialFit(times, xs)\n const fitY = this.polynomialFit(times, ys)\n\n const lastT = times[times.length - 1]\n\n return {\n velocity: {\n x: fitX.b + 2 * fitX.c * lastT,\n y: fitY.b + 2 * fitY.c * lastT,\n },\n acceleration: {\n x: 2 * fitX.c,\n y: 2 * fitY.c,\n },\n }\n }\n\n /**\n * Quadratic polynomial least squares fitting\n * y = a + b*x + c*x^2\n */\n private polynomialFit(\n x: number[],\n y: number[]\n ): { a: number; b: number; c: number } {\n const n = x.length\n\n // Build normal equation coefficient matrix\n let sumX = 0,\n sumX2 = 0,\n sumX3 = 0,\n sumX4 = 0\n let sumY = 0,\n sumXY = 0,\n sumX2Y = 0\n\n for (let i = 0; i < n; i++) {\n const xi = x[i]\n const yi = y[i]\n const xi2 = xi * xi\n sumX += xi\n sumX2 += xi2\n sumX3 += xi2 * xi\n sumX4 += xi2 * xi2\n sumY += yi\n sumXY += xi * yi\n sumX2Y += xi2 * yi\n }\n\n // Solve system of equations (Cramer's rule)\n // [n, sumX, sumX2 ] [a] [sumY ]\n // [sumX, sumX2, sumX3 ] [b] = [sumXY ]\n // [sumX2, sumX3, sumX4 ] [c] [sumX2Y]\n\n const det =\n n * (sumX2 * sumX4 - sumX3 * sumX3) -\n sumX * (sumX * sumX4 - sumX3 * sumX2) +\n sumX2 * (sumX * sumX3 - sumX2 * sumX2)\n\n if (Math.abs(det) < 1e-10) {\n // Fall back to linear fit if matrix is singular\n const avgX = sumX / n\n const avgY = sumY / n\n let num = 0,\n den = 0\n for (let i = 0; i < n; i++) {\n num += (x[i] - avgX) * (y[i] - avgY)\n den += (x[i] - avgX) * (x[i] - avgX)\n }\n const b = den > 0 ? num / den : 0\n const a = avgY - b * avgX\n return { a, b, c: 0 }\n }\n\n const a =\n (sumY * (sumX2 * sumX4 - sumX3 * sumX3) -\n sumX * (sumXY * sumX4 - sumX3 * sumX2Y) +\n sumX2 * (sumXY * sumX3 - sumX2 * sumX2Y)) /\n det\n\n const b =\n (n * (sumXY * sumX4 - sumX3 * sumX2Y) -\n sumY * (sumX * sumX4 - sumX3 * sumX2) +\n sumX2 * (sumX * sumX2Y - sumXY * sumX2)) /\n det\n\n const c =\n (n * (sumX2 * sumX2Y - sumXY * sumX3) -\n sumX * (sumX * sumX2Y - sumXY * sumX2) +\n sumY * (sumX * sumX3 - sumX2 * sumX2)) /\n det\n\n return { a, b, c }\n }\n\n updateParams(params: Partial<LinearPredictionFilterParams>): void {\n this.params = { ...this.params, ...params }\n }\n\n reset(): void {\n this.history = []\n this.lastOutput = null\n }\n}\n\n/**\n * Create a linear prediction filter\n *\n * Compensates for filter-induced latency by estimating velocity and\n * acceleration from past positions to predict the next position.\n *\n * @example\n * ```ts\n * // Standard settings\n * const pointer = new StabilizedPointer()\n * .addFilter(linearPredictionFilter({\n * historySize: 4,\n * predictionFactor: 0.5\n * }))\n *\n * // Strong prediction (prioritize latency reduction)\n * const responsive = linearPredictionFilter({\n * historySize: 3,\n * predictionFactor: 0.8,\n * smoothing: 0.7\n * })\n *\n * // Stability focused\n * const stable = linearPredictionFilter({\n * historySize: 5,\n * predictionFactor: 0.3,\n * smoothing: 0.5\n * })\n * ```\n */\nexport function linearPredictionFilter(\n params: LinearPredictionFilterParams\n): Filter {\n return new LinearPredictionFilterImpl(params)\n}\n","import type { Filter, PointerPoint, UpdatableFilter } from '../types'\n\nexport interface DouglasPeuckerFilterParams {\n /** Distance threshold (px). Points with perpendicular distance less than this are removed */\n epsilon: number\n}\n\nconst FILTER_TYPE = 'douglasPeucker' as const\n\n/**\n * Douglas-Peucker simplification filter\n *\n * Reduces the number of points in a stroke while preserving its shape.\n * Points are collected in a buffer and simplified when flush() is called.\n *\n * Note: This filter collects all points and simplifies on flush/reset.\n * For real-time use, call getSimplified() to get the current simplified result.\n */\nclass DouglasPeuckerFilterImpl implements UpdatableFilter<DouglasPeuckerFilterParams> {\n readonly type = FILTER_TYPE\n private params: DouglasPeuckerFilterParams\n private points: PointerPoint[] = []\n\n constructor(params: DouglasPeuckerFilterParams) {\n this.params = { ...params }\n }\n\n process(point: PointerPoint): PointerPoint | null {\n this.points.push(point)\n return point\n }\n\n updateParams(params: Partial<DouglasPeuckerFilterParams>): void {\n this.params = { ...this.params, ...params }\n }\n\n reset(): void {\n this.points = []\n }\n\n /**\n * Get the simplified points without clearing the buffer\n */\n getSimplified(): PointerPoint[] {\n if (this.points.length <= 2) {\n return [...this.points]\n }\n return simplifyDouglasPeucker(this.points, this.params.epsilon)\n }\n\n /**\n * Get all collected points\n */\n getPoints(): PointerPoint[] {\n return [...this.points]\n }\n}\n\n/**\n * Calculate perpendicular distance from a point to a line\n */\nfunction perpendicularDistance(\n point: PointerPoint,\n lineStart: PointerPoint,\n lineEnd: PointerPoint\n): number {\n const dx = lineEnd.x - lineStart.x\n const dy = lineEnd.y - lineStart.y\n\n // Line length squared\n const lengthSq = dx * dx + dy * dy\n\n if (lengthSq === 0) {\n // Start and end are the same point\n const pdx = point.x - lineStart.x\n const pdy = point.y - lineStart.y\n return Math.sqrt(pdx * pdx + pdy * pdy)\n }\n\n // Calculate the perpendicular distance using cross product\n const numerator = Math.abs(\n dy * point.x -\n dx * point.y +\n lineEnd.x * lineStart.y -\n lineEnd.y * lineStart.x\n )\n return numerator / Math.sqrt(lengthSq)\n}\n\n/**\n * Douglas-Peucker simplification algorithm\n */\nfunction simplifyDouglasPeucker(\n points: PointerPoint[],\n epsilon: number\n): PointerPoint[] {\n if (points.length <= 2) {\n return points\n }\n\n // Find the point with the maximum distance from the line\n let maxDistance = 0\n let maxIndex = 0\n const first = points[0]\n const last = points[points.length - 1]\n\n for (let i = 1; i < points.length - 1; i++) {\n const distance = perpendicularDistance(points[i], first, last)\n if (distance > maxDistance) {\n maxDistance = distance\n maxIndex = i\n }\n }\n\n // If max distance is greater than epsilon, recursively simplify\n if (maxDistance > epsilon) {\n // Recursive call\n const left = simplifyDouglasPeucker(points.slice(0, maxIndex + 1), epsilon)\n const right = simplifyDouglasPeucker(points.slice(maxIndex), epsilon)\n\n // Combine results (remove duplicate point at join)\n return [...left.slice(0, -1), ...right]\n }\n\n // Return only start and end points\n return [first, last]\n}\n\n/**\n * Create a Douglas-Peucker simplification filter\n *\n * This filter reduces the number of points while preserving the overall shape\n * of the stroke. Useful for reducing data size or smoothing jaggy lines.\n *\n * @example\n * ```ts\n * const pointer = new StabilizedPointer()\n * .addFilter(douglasPeuckerFilter({ epsilon: 2 }))\n *\n * // Process points\n * points.forEach(p => pointer.process(p))\n *\n * // Get simplified result\n * const filter = pointer.getFilter('douglasPeucker') as ReturnType<typeof douglasPeuckerFilter>\n * const simplified = filter.getSimplified()\n * ```\n */\nexport function douglasPeuckerFilter(\n params: DouglasPeuckerFilterParams\n): Filter {\n return new DouglasPeuckerFilterImpl(params)\n}\n\n/**\n * Simplify an array of points using the Douglas-Peucker algorithm\n *\n * Standalone function for one-shot simplification without using a filter.\n *\n * @example\n * ```ts\n * const simplified = simplify(points, 2)\n * ```\n */\nexport function simplify(\n points: PointerPoint[],\n epsilon: number\n): PointerPoint[] {\n if (points.length <= 2) {\n return [...points]\n }\n return simplifyDouglasPeucker(points, epsilon)\n}\n","import { StabilizedPointer } from './StabilizedPointer'\nimport { noiseFilter } from './filters/NoiseFilter'\nimport { kalmanFilter } from './filters/KalmanFilter'\nimport { movingAverageFilter } from './filters/MovingAverageFilter'\nimport { stringFilter } from './filters/StringFilter'\n\n/**\n * Create a StabilizedPointer based on level (0-100)\n *\n * Filter configuration by level:\n * - 0%: No stabilization\n * - 1-20%: Noise filter only\n * - 21-40%: Noise + Kalman\n * - 41-60%: Noise + Kalman + Moving average\n * - 61-80%: Above + String (light)\n * - 81-100%: Above + String (strong)\n *\n * @example\n * ```ts\n * // Medium stabilization\n * const pointer = createStabilizedPointer(50)\n *\n * // No stabilization\n * const raw = createStabilizedPointer(0)\n * ```\n */\nexport function createStabilizedPointer(level: number): StabilizedPointer {\n const clampedLevel = Math.max(0, Math.min(100, level))\n const pointer = new StabilizedPointer()\n\n if (clampedLevel === 0) {\n return pointer\n }\n\n // Scale: level 50 = max effect (t=1.0), level 100 = same as level 50\n const t = Math.min(clampedLevel / 50, 1.0)\n\n // Level 1-100: Noise filter\n const minDistance = 1.0 + t * 2.0 // 1.0-3.0\n pointer.addFilter(noiseFilter({ minDistance }))\n\n if (clampedLevel >= 21) {\n // Level 21-100: Kalman filter\n const processNoise = 0.12 - t * 0.08 // 0.12-0.04\n const measurementNoise = 0.4 + t * 0.6 // 0.4-1.0\n pointer.addFilter(kalmanFilter({ processNoise, measurementNoise }))\n }\n\n if (clampedLevel >= 41) {\n // Level 41-100: Moving average filter\n const windowSize = clampedLevel >= 61 ? 7 : 5\n pointer.addFilter(movingAverageFilter({ windowSize }))\n }\n\n if (clampedLevel >= 61) {\n // Level 61-100: String stabilization\n const stringLength = clampedLevel >= 81 ? 15 : 8\n pointer.addFilter(stringFilter({ stringLength }))\n }\n\n return pointer\n}\n\n/**\n * Create StabilizedPointer from preset name\n */\nexport type PresetName = 'none' | 'light' | 'medium' | 'heavy' | 'extreme'\n\nconst presetLevels: Record<PresetName, number> = {\n none: 0,\n light: 20,\n medium: 50,\n heavy: 75,\n extreme: 100,\n}\n\n/**\n * Create StabilizedPointer from preset\n *\n * @example\n * ```ts\n * const pointer = createFromPreset('medium')\n * ```\n */\nexport function createFromPreset(preset: PresetName): StabilizedPointer {\n return createStabilizedPointer(presetLevels[preset])\n}\n","import type { Kernel } from './types'\n\nexport interface GaussianKernelParams {\n /** Kernel size (odd number) */\n size: number\n /** Standard deviation (default: size / 3) */\n sigma?: number\n}\n\n/**\n * Generate a Gaussian kernel\n *\n * @example\n * ```ts\n * const kernel = gaussianKernel({ size: 7, sigma: 2 })\n * ```\n */\nexport function gaussianKernel(params: GaussianKernelParams): Kernel {\n const { size } = params\n const sigma = params.sigma ?? size / 3\n\n // Force odd size\n const actualSize = size % 2 === 0 ? size + 1 : size\n const halfSize = Math.floor(actualSize / 2)\n\n const weights: number[] = []\n let sum = 0\n\n for (let i = 0; i < actualSize; i++) {\n const x = i - halfSize\n const weight = Math.exp(-(x * x) / (2 * sigma * sigma))\n weights.push(weight)\n sum += weight\n }\n\n // Normalize\n for (let i = 0; i < weights.length; i++) {\n weights[i] /= sum\n }\n\n return {\n type: 'gaussian',\n weights,\n }\n}\n","import type { Kernel } from './types'\n\nexport interface BoxKernelParams {\n /** Kernel size (odd number) */\n size: number\n}\n\n/**\n * Generate a box kernel (simple average)\n *\n * @example\n * ```ts\n * const kernel = boxKernel({ size: 5 })\n * // weights: [0.2, 0.2, 0.2, 0.2, 0.2]\n * ```\n */\nexport function boxKernel(params: BoxKernelParams): Kernel {\n const { size } = params\n\n // Force odd size\n const actualSize = size % 2 === 0 ? size + 1 : size\n const weight = 1 / actualSize\n\n const weights = Array(actualSize).fill(weight)\n\n return {\n type: 'box',\n weights,\n }\n}\n","import type { Kernel } from './types'\n\nexport interface TriangleKernelParams {\n /** Kernel size (odd number) */\n size: number\n}\n\n/**\n * Generate a triangle kernel (center-weighted)\n *\n * @example\n * ```ts\n * const kernel = triangleKernel({ size: 5 })\n * // weights: [1/9, 2/9, 3/9, 2/9, 1/9] (normalized)\n * ```\n */\nexport function triangleKernel(params: TriangleKernelParams): Kernel {\n const { size } = params\n\n // Force odd size\n const actualSize = size % 2 === 0 ? size + 1 : size\n const halfSize = Math.floor(actualSize / 2)\n\n const weights: number[] = []\n let sum = 0\n\n for (let i = 0; i < actualSize; i++) {\n // Maximum at center, decreasing toward edges\n const weight = halfSize + 1 - Math.abs(i - halfSize)\n weights.push(weight)\n sum += weight\n }\n\n // Normalize\n for (let i = 0; i < weights.length; i++) {\n weights[i] /= sum\n }\n\n return {\n type: 'triangle',\n weights,\n }\n}\n","import type { Point } from '../types'\n\nexport interface BilateralKernelParams {\n /**\n * Kernel size (odd number)\n * Recommended: 5-11\n */\n size: number\n /**\n * Spatial standard deviation\n * Weight decay based on index distance\n * Recommended: size / 3\n */\n sigmaSpace?: number\n /**\n * Value standard deviation\n * Weight decay based on coordinate value difference (edge preservation)\n * Lower = stronger edge preservation\n * Recommended: 5-30\n */\n sigmaValue: number\n}\n\nexport interface BilateralKernel {\n readonly type: 'bilateral'\n readonly size: number\n readonly sigmaSpace: number\n readonly sigmaValue: number\n /**\n * Dynamically compute weights for each point\n */\n computeWeights(center: Point, neighbors: Point[]): number[]\n}\n\n/**\n * Generate a bilateral kernel\n *\n * Unlike standard convolution, weights are determined considering\n * coordinate value similarity. This enables smoothing while preserving\n * edges (sharp direction changes, etc.).\n *\n * @example\n * ```ts\n * const kernel = bilateralKernel({\n * size: 7,\n * sigmaValue: 10\n * })\n * ```\n */\nexport function bilateralKernel(\n params: BilateralKernelParams\n): BilateralKernel {\n const { size, sigmaValue } = params\n const actualSize = size % 2 === 0 ? size + 1 : size\n const sigmaSpace = params.sigmaSpace ?? actualSize / 3\n\n const halfSize = Math.floor(actualSize / 2)\n\n // Pre-compute spatial weights\n const spatialWeights: number[] = []\n for (let i = 0; i < actualSize; i++) {\n const d = i - halfSize\n spatialWeights.push(Math.exp(-(d * d) / (2 * sigmaSpace * sigmaSpace)))\n }\n\n return {\n type: 'bilateral',\n size: actualSize,\n sigmaSpace,\n sigmaValue,\n\n computeWeights(center: Point, neighbors: Point[]): number[] {\n const weights: number[] = []\n let sum = 0\n\n for (let i = 0; i < neighbors.length; i++) {\n // Calculate coordinate value difference\n const dx = neighbors[i].x - center.x\n const dy = neighbors[i].y - center.y\n const valueDiff = dx * dx + dy * dy\n\n // Value-based weight\n const valueWeight = Math.exp(-valueDiff / (2 * sigmaValue * sigmaValue))\n\n // Spatial × value weight\n const weight = spatialWeights[i] * valueWeight\n weights.push(weight)\n sum += weight\n }\n\n // Normalize\n if (sum > 0) {\n for (let i = 0; i < weights.length; i++) {\n weights[i] /= sum\n }\n }\n\n return weights\n },\n }\n}\n","import type { Point } from './types'\n\nexport interface ToSVGPathOptions {\n /**\n * Number of decimal places for coordinates\n * @default 2\n */\n precision?: number\n\n /**\n * Use relative commands (lowercase) instead of absolute (uppercase)\n * @default false\n */\n relative?: boolean\n\n /**\n * Close the path with 'Z' command\n * @default false\n */\n closePath?: boolean\n}\n\n/**\n * Convert an array of points to an SVG path string\n *\n * Generates a polyline path (M for move, L for lines).\n * For smoother curves, consider using `toSVGPathSmooth` which uses\n * quadratic Bezier curves.\n *\n * @example\n * ```ts\n * const points = stabilizer.finish()\n * const pathData = toSVGPath(points)\n * // \"M 10 20 L 15 25 L 20 30\"\n *\n * // Use in SVG\n * svg.innerHTML = `<path d=\"${pathData}\" stroke=\"black\" fill=\"none\"/>`\n * ```\n */\nexport function toSVGPath(\n points: Point[],\n options: ToSVGPathOptions = {}\n): string {\n const { precision = 2, relative = false, closePath = false } = options\n\n if (points.length === 0) {\n return ''\n }\n\n const format = (n: number) => n.toFixed(precision)\n\n const parts: string[] = []\n\n // Move to first point\n const first = points[0]\n parts.push(`${relative ? 'm' : 'M'} ${format(first.x)} ${format(first.y)}`)\n\n if (relative) {\n // Relative commands\n let prevX = first.x\n let prevY = first.y\n\n for (let i = 1; i < points.length; i++) {\n const p = points[i]\n const dx = p.x - prevX\n const dy = p.y - prevY\n parts.push(`l ${format(dx)} ${format(dy)}`)\n prevX = p.x\n prevY = p.y\n }\n } else {\n // Absolute commands\n for (let i = 1; i < points.length; i++) {\n const p = points[i]\n parts.push(`L ${format(p.x)} ${format(p.y)}`)\n }\n }\n\n if (closePath) {\n parts.push(relative ? 'z' : 'Z')\n }\n\n return parts.join(' ')\n}\n\nexport interface ToSVGPathSmoothOptions extends ToSVGPathOptions {\n /**\n * Smoothing tension (0-1). Higher values create tighter curves.\n * @default 0.5\n */\n tension?: number\n}\n\n/**\n * Convert an array of points to a smooth SVG path using quadratic Bezier curves\n *\n * Creates smoother curves by calculating control points between segments.\n * Better for artistic strokes compared to simple polylines.\n *\n * @example\n * ```ts\n * const points = stabilizer.finish()\n * const pathData = toSVGPathSmooth(points, { tension: 0.3 })\n * // \"M 10 20 Q 12.5 22.5 15 25 Q 17.5 27.5 20 30\"\n * ```\n */\nexport function toSVGPathSmooth(\n points: Point[],\n options: ToSVGPathSmoothOptions = {}\n): string {\n const {\n precision = 2,\n relative = false,\n closePath = false,\n tension = 0.5,\n } = options\n\n if (points.length === 0) {\n return ''\n }\n\n if (points.length === 1) {\n const p = points[0]\n return `${relative ? 'm' : 'M'} ${p.x.toFixed(precision)} ${p.y.toFixed(precision)}`\n }\n\n if (points.length === 2) {\n return toSVGPath(points, options)\n }\n\n const format = (n: number) => n.toFixed(precision)\n const parts: string[] = []\n\n // Move to first point\n const first = points[0]\n parts.push(`${relative ? 'm' : 'M'} ${format(first.x)} ${format(first.y)}`)\n\n // Use quadratic bezier curves for smooth path\n // Control point is calculated as midpoint adjusted by tension\n for (let i = 1; i < points.length - 1; i++) {\n const prev = points[i - 1]\n const curr = points[i]\n const next = points[i + 1]\n\n // Midpoint between current and next\n const midX = (curr.x + next.x) / 2\n const midY = (curr.y + next.y) / 2\n\n // Control point influenced by tension\n const cpX = curr.x + (midX - curr.x) * (1 - tension)\n const cpY = curr.y + (midY - curr.y) * (1 - tension)\n\n if (relative) {\n const dx = cpX - prev.x\n const dy = cpY - prev.y\n const endDx = midX - prev.x\n const endDy = midY - prev.y\n parts.push(\n `q ${format(dx)} ${format(dy)} ${format(endDx)} ${format(endDy)}`\n )\n } else {\n parts.push(\n `Q ${format(cpX)} ${format(cpY)} ${format(midX)} ${format(midY)}`\n )\n }\n }\n\n // Line to last point\n const last = points[points.length - 1]\n const secondLast = points[points.length - 2]\n if (relative) {\n const midX = (secondLast.x + last.x) / 2\n const midY = (secondLast.y + last.y) / 2\n parts.push(`l ${format(last.x - midX)} ${format(last.y - midY)}`)\n } else {\n parts.push(`L ${format(last.x)} ${format(last.y)}`)\n }\n\n if (closePath) {\n parts.push(relative ? 'z' : 'Z')\n }\n\n return parts.join(' ')\n}\n\nexport interface ToSVGPathCubicOptions extends ToSVGPathOptions {\n /**\n * Smoothing factor for control points (0-1)\n * @default 0.25\n */\n smoothing?: number\n}\n\n/**\n * Convert an array of points to a smooth SVG path using cubic Bezier curves\n *\n * Uses Catmull-Rom to Bezier conversion for smoother curves than quadratic.\n * Best quality for artistic strokes.\n *\n * @example\n * ```ts\n * const points = stabilizer.finish()\n * const pathData = toSVGPathCubic(points)\n * // \"M 10 20 C 12 21 14 24 15 25 C 16 26 18 29 20 30\"\n * ```\n */\nexport function toSVGPathCubic(\n points: Point[],\n options: ToSVGPathCubicOptions = {}\n): string {\n const {\n precision = 2,\n relative = false,\n closePath = false,\n smoothing = 0.25,\n } = options\n\n if (points.length === 0) {\n return ''\n }\n\n if (points.length === 1) {\n const p = points[0]\n return `${relative ? 'm' : 'M'} ${p.x.toFixed(precision)} ${p.y.toFixed(precision)}`\n }\n\n if (points.length === 2) {\n return toSVGPath(points, options)\n }\n\n const format = (n: number) => n.toFixed(precision)\n const parts: string[] = []\n\n // Move to first point\n const first = points[0]\n parts.push(`${relative ? 'm' : 'M'} ${format(first.x)} ${format(first.y)}`)\n\n // Calculate control points using Catmull-Rom to Bezier conversion\n for (let i = 0; i < points.length - 1; i++) {\n const p0 = points[Math.max(0, i - 1)]\n const p1 = points[i]\n const p2 = points[i + 1]\n const p3 = points[Math.min(points.length - 1, i + 2)]\n\n // Control points\n const cp1x = p1.x + (p2.x - p0.x) * smoothing\n const cp1y = p1.y + (p2.y - p0.y) * smoothing\n const cp2x = p2.x - (p3.x - p1.x) * smoothing\n const cp2y = p2.y - (p3.y - p1.y) * smoothing\n\n if (relative) {\n const dx1 = cp1x - p1.x\n const dy1 = cp1y - p1.y\n const dx2 = cp2x - p1.x\n const dy2 = cp2y - p1.y\n const dx = p2.x - p1.x\n const dy = p2.y - p1.y\n parts.push(\n `c ${format(dx1)} ${format(dy1)} ${format(dx2)} ${format(dy2)} ${format(dx)} ${format(dy)}`\n )\n } else {\n parts.push(\n `C ${format(cp1x)} ${format(cp1y)} ${format(cp2x)} ${format(cp2y)} ${format(p2.x)} ${format(p2.y)}`\n )\n }\n }\n\n if (closePath) {\n parts.push(relative ? 'z' : 'Z')\n }\n\n return parts.join(' ')\n}\n","import type { PointerPoint } from './types'\n\nexport interface PredictionConfig {\n /**\n * Number of historical points to use for velocity estimation\n * @default 4\n */\n historySize?: number\n\n /**\n * Maximum prediction time in milliseconds\n * @default 50\n */\n maxPredictionMs?: number\n\n /**\n * Minimum velocity (px/ms) required to generate a prediction\n * @default 0.1\n */\n minVelocity?: number\n}\n\ninterface PointWithVelocity {\n point: PointerPoint\n vx: number\n vy: number\n}\n\n/**\n * Stroke predictor for reducing perceived latency\n *\n * Estimates the next pen position based on recent velocity,\n * useful for showing a \"preview\" stroke ahead of the actual stabilized input.\n *\n * @example\n * ```ts\n * const predictor = new StrokePredictor({ historySize: 4 })\n *\n * canvas.onpointermove = (e) => {\n * const point = { x: e.clientX, y: e.clientY, timestamp: e.timeStamp }\n * const stabilized = pointer.process(point)\n *\n * if (stabilized) {\n * predictor.addPoint(stabilized)\n *\n * // Get predicted point 16ms ahead\n * const predicted = predictor.predict(16)\n * if (predicted) {\n * drawPreview(predicted)\n * }\n * }\n * }\n * ```\n */\nexport class StrokePredictor {\n private config: Required<PredictionConfig>\n private history: PointWithVelocity[] = []\n\n constructor(config: PredictionConfig = {}) {\n this.config = {\n historySize: config.historySize ?? 4,\n maxPredictionMs: config.maxPredictionMs ?? 50,\n minVelocity: config.minVelocity ?? 0.1,\n }\n }\n\n /**\n * Add a point to the predictor's history\n */\n addPoint(point: PointerPoint): void {\n let vx = 0\n let vy = 0\n\n if (this.history.length > 0) {\n const last = this.history[this.history.length - 1]\n const dt = point.timestamp - last.point.timestamp\n\n if (dt > 0) {\n vx = (point.x - last.point.x) / dt\n vy = (point.y - last.point.y) / dt\n }\n }\n\n this.history.push({ point, vx, vy })\n\n // Keep history within size limit\n while (this.history.length > this.config.historySize) {\n this.history.shift()\n }\n }\n\n /**\n * Predict the position at a given time ahead\n *\n * @param lookAheadMs Time in milliseconds to predict ahead\n * @returns Predicted point, or null if prediction is not possible\n */\n predict(lookAheadMs: number): PointerPoint | null {\n if (this.history.length < 2) {\n return null\n }\n\n // Clamp prediction time\n const actualLookAhead = Math.min(lookAheadMs, this.config.maxPredictionMs)\n\n // Calculate weighted average velocity\n const { avgVx, avgVy } = this.calculateAverageVelocity()\n\n // Check minimum velocity threshold\n const speed = Math.sqrt(avgVx * avgVx + avgVy * avgVy)\n if (speed < this.config.minVelocity) {\n return null\n }\n\n const lastPoint = this.history[this.history.length - 1].point\n\n // Predict position\n const predictedX = lastPoint.x + avgVx * actualLookAhead\n const predictedY = lastPoint.y + avgVy * actualLookAhead\n\n return {\n x: predictedX,\n y: predictedY,\n pressure: lastPoint.pressure,\n tiltX: lastPoint.tiltX,\n tiltY: lastPoint.tiltY,\n timestamp: lastPoint.timestamp + actualLookAhead,\n }\n }\n\n /**\n * Get predicted points for rendering a preview stroke\n *\n * @param count Number of predicted points to generate\n * @param intervalMs Time interval between predicted points\n * @returns Array of predicted points\n */\n predictMultiple(count: number, intervalMs: number = 8): PointerPoint[] {\n const points: PointerPoint[] = []\n\n for (let i = 1; i <= count; i++) {\n const predicted = this.predict(i * intervalMs)\n if (predicted) {\n points.push(predicted)\n }\n }\n\n return points\n }\n\n /**\n * Calculate weighted average velocity from history\n * Recent points are weighted more heavily\n */\n private calculateAverageVelocity(): { avgVx: number; avgVy: number } {\n if (this.history.length < 2) {\n return { avgVx: 0, avgVy: 0 }\n }\n\n let totalWeight = 0\n let weightedVx = 0\n let weightedVy = 0\n\n // Skip first point (no velocity data)\n for (let i = 1; i < this.history.length; i++) {\n // Exponential weighting: more recent = higher weight\n const weight = Math.pow(2, i)\n weightedVx += this.history[i].vx * weight\n weightedVy += this.history[i].vy * weight\n totalWeight += weight\n }\n\n return {\n avgVx: totalWeight > 0 ? weightedVx / totalWeight : 0,\n avgVy: totalWeight > 0 ? weightedVy / totalWeight : 0,\n }\n }\n\n /**\n * Get the current average velocity\n */\n getVelocity(): { vx: number; vy: number } {\n const { avgVx, avgVy } = this.calculateAverageVelocity()\n return { vx: avgVx, vy: avgVy }\n }\n\n /**\n * Get the current speed (magnitude of velocity)\n */\n getSpeed(): number {\n const { avgVx, avgVy } = this.calculateAverageVelocity()\n return Math.sqrt(avgVx * avgVx + avgVy * avgVy)\n }\n\n /**\n * Clear prediction history\n */\n reset(): void {\n this.history = []\n }\n\n /**\n * Get number of points in history\n */\n get historyLength(): number {\n return this.history.length\n }\n}\n\n/**\n * Create a stroke predictor\n *\n * @example\n * ```ts\n * const predictor = createPredictor({ historySize: 4, maxPredictionMs: 32 })\n * ```\n */\nexport function createPredictor(config?: PredictionConfig): StrokePredictor {\n return new StrokePredictor(config)\n}\n","import type { Point, PointerPoint } from './types'\n\nexport interface CatmullRomOptions {\n /**\n * Tension parameter (0-1)\n * - 0: Looser curves (Uniform Catmull-Rom)\n * - 0.5: Standard Catmull-Rom (Centripetal)\n * - 1: Tighter curves (Chordal)\n * @default 0.5\n */\n tension?: number\n\n /**\n * Number of interpolated points between each input point pair\n * @default 10\n */\n segmentDivisions?: number\n\n /**\n * Whether to interpolate pressure and tilt values\n * @default true\n */\n interpolateAttributes?: boolean\n}\n\n/**\n * Catmull-Rom spline interpolation for smooth curve generation\n *\n * Creates smooth curves through a series of points using the Catmull-Rom\n * spline algorithm. Useful for upsampling stroke data or generating\n * smooth paths for rendering.\n *\n * @example\n * ```ts\n * const points = stabilizer.finish()\n * const smooth = interpolateCatmullRom(points, { segmentDivisions: 5 })\n * // smooth now has ~5x more points with smooth interpolation\n * ```\n */\nexport function interpolateCatmullRom(\n points: PointerPoint[],\n options: CatmullRomOptions = {}\n): PointerPoint[] {\n const {\n tension = 0.5,\n segmentDivisions = 10,\n interpolateAttributes = true,\n } = options\n\n if (points.length < 2) {\n return [...points]\n }\n\n if (points.length === 2) {\n // Linear interpolation for 2 points\n return interpolateLinear(points[0], points[1], segmentDivisions)\n }\n\n const result: PointerPoint[] = []\n\n // Process each segment\n for (let i = 0; i < points.length - 1; i++) {\n // Get control points (p0, p1, p2, p3)\n const p0 = points[Math.max(0, i - 1)]\n const p1 = points[i]\n const p2 = points[i + 1]\n const p3 = points[Math.min(points.length - 1, i + 2)]\n\n // Add the start point of the segment\n if (i === 0) {\n result.push(p1)\n }\n\n // Interpolate segment\n for (let j = 1; j <= segmentDivisions; j++) {\n const t = j / segmentDivisions\n\n // Calculate position using Catmull-Rom formula\n const point = catmullRomPoint(p0, p1, p2, p3, t, tension)\n\n // Interpolate attributes if requested\n if (interpolateAttributes) {\n point.pressure = lerpOptional(p1.pressure, p2.pressure, t)\n point.tiltX = lerpOptional(p1.tiltX, p2.tiltX, t)\n point.tiltY = lerpOptional(p1.tiltY, p2.tiltY, t)\n }\n\n // Interpolate timestamp\n point.timestamp = lerp(p1.timestamp, p2.timestamp, t)\n\n result.push(point)\n }\n }\n\n return result\n}\n\n/**\n * Calculate a single point on a Catmull-Rom spline\n */\nfunction catmullRomPoint(\n p0: Point,\n p1: Point,\n p2: Point,\n p3: Point,\n t: number,\n tension: number\n): PointerPoint {\n // Centripetal Catmull-Rom parameterization\n const alpha = tension\n\n // Calculate knot values\n const t0 = 0\n const t1 = getKnotValue(t0, p0, p1, alpha)\n const t2 = getKnotValue(t1, p1, p2, alpha)\n const t3 = getKnotValue(t2, p2, p3, alpha)\n\n // Remap t from [0,1] to [t1, t2]\n const tActual = lerp(t1, t2, t)\n\n // Calculate the point using Barry and Goldman's pyramidal formulation\n const A1x = remapPoint(t0, t1, p0.x, p1.x, tActual)\n const A1y = remapPoint(t0, t1, p0.y, p1.y, tActual)\n const A2x = remapPoint(t1, t2, p1.x, p2.x, tActual)\n const A2y = remapPoint(t1, t2, p1.y, p2.y, tActual)\n const A3x = remapPoint(t2, t3, p2.x, p3.x, tActual)\n const A3y = remapPoint(t2, t3, p2.y, p3.y, tActual)\n\n const B1x = remapPoint(t0, t2, A1x, A2x, tActual)\n const B1y = remapPoint(t0, t2, A1y, A2y, tActual)\n const B2x = remapPoint(t1, t3, A2x, A3x, tActual)\n const B2y = remapPoint(t1, t3, A2y, A3y, tActual)\n\n const Cx = remapPoint(t1, t2, B1x, B2x, tActual)\n const Cy = remapPoint(t1, t2, B1y, B2y, tActual)\n\n return {\n x: Cx,\n y: Cy,\n timestamp: 0, // Will be set by caller\n }\n}\n\n/**\n * Calculate knot interval based on chord length\n */\nfunction getKnotValue(ti: number, p0: Point, p1: Point, alpha: number): number {\n const dx = p1.x - p0.x\n const dy = p1.y - p0.y\n const dist = Math.sqrt(dx * dx + dy * dy)\n return ti + Math.pow(dist, alpha)\n}\n\n/**\n * Remap a value between two points at a given parameter\n */\nfunction remapPoint(\n t0: number,\n t1: number,\n p0: number,\n p1: number,\n t: number\n): number {\n const denominator = t1 - t0\n if (Math.abs(denominator) < 1e-10) {\n return p0\n }\n return ((t1 - t) / denominator) * p0 + ((t - t0) / denominator) * p1\n}\n\n/**\n * Linear interpolation\n */\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t\n}\n\n/**\n * Linear interpolation for optional values\n */\nfunction lerpOptional(\n a: number | undefined,\n b: number | undefined,\n t: number\n): number | undefined {\n if (a === undefined || b === undefined) {\n return undefined\n }\n return lerp(a, b, t)\n}\n\n/**\n * Linear interpolation between two points\n */\nfunction interpolateLinear(\n p1: PointerPoint,\n p2: PointerPoint,\n divisions: number\n): PointerPoint[] {\n const result: PointerPoint[] = [p1]\n\n for (let i = 1; i <= divisions; i++) {\n const t = i / divisions\n result.push({\n x: lerp(p1.x, p2.x, t),\n y: lerp(p1.y, p2.y, t),\n pressure: lerpOptional(p1.pressure, p2.pressure, t),\n tiltX: lerpOptional(p1.tiltX, p2.tiltX, t),\n tiltY: lerpOptional(p1.tiltY, p2.tiltY, t),\n timestamp: lerp(p1.timestamp, p2.timestamp, t),\n })\n }\n\n return result\n}\n\n/**\n * Calculate the total arc length of a set of points\n */\nexport function calculateArcLength(points: Point[]): number {\n if (points.length < 2) {\n return 0\n }\n\n let length = 0\n for (let i = 1; i < points.length; i++) {\n const dx = points[i].x - points[i - 1].x\n const dy = points[i].y - points[i - 1].y\n length += Math.sqrt(dx * dx + dy * dy)\n }\n return length\n}\n\n/**\n * Resample points at uniform arc length intervals\n *\n * Creates a new set of points where each point is at an equal distance\n * along the curve. Useful for consistent stroke rendering.\n *\n * @example\n * ```ts\n * const uniform = resampleByArcLength(points, 5) // 5px between points\n * ```\n */\nexport function resampleByArcLength(\n points: PointerPoint[],\n spacing: number\n): PointerPoint[] {\n if (points.length < 2) {\n return [...points]\n }\n\n const result: PointerPoint[] = [points[0]]\n let accumulated = 0\n\n for (let i = 1; i < points.length; i++) {\n const prev = points[i - 1]\n const curr = points[i]\n const dx = curr.x - prev.x\n const dy = curr.y - prev.y\n const segmentLength = Math.sqrt(dx * dx + dy * dy)\n\n accumulated += segmentLength\n\n while (accumulated >= spacing) {\n const overshoot = accumulated - spacing\n const ratio = (segmentLength - overshoot) / segmentLength\n const t = 1 - overshoot / segmentLength\n\n result.push({\n x: prev.x + dx * ratio,\n y: prev.y + dy * ratio,\n pressure: lerpOptional(prev.pressure, curr.pressure, t),\n tiltX: lerpOptional(prev.tiltX, curr.tiltX, t),\n tiltY: lerpOptional(prev.tiltY, curr.tiltY, t),\n timestamp: lerp(prev.timestamp, curr.timestamp, t),\n })\n\n accumulated -= spacing\n }\n }\n\n // Add the last point if it's not too close to the previous\n const last = points[points.length - 1]\n const lastResult = result[result.length - 1]\n const finalDx = last.x - lastResult.x\n const finalDy = last.y - lastResult.y\n const finalDist = Math.sqrt(finalDx * finalDx + finalDy * finalDy)\n\n if (finalDist > spacing * 0.5) {\n result.push(last)\n }\n\n return result\n}\n"],"names":["FILTER_TYPE","b","a"],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { PointerPoint } from './types';
|
|
2
|
+
export interface PredictionConfig {
|
|
3
|
+
/**
|
|
4
|
+
* Number of historical points to use for velocity estimation
|
|
5
|
+
* @default 4
|
|
6
|
+
*/
|
|
7
|
+
historySize?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Maximum prediction time in milliseconds
|
|
10
|
+
* @default 50
|
|
11
|
+
*/
|
|
12
|
+
maxPredictionMs?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Minimum velocity (px/ms) required to generate a prediction
|
|
15
|
+
* @default 0.1
|
|
16
|
+
*/
|
|
17
|
+
minVelocity?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Stroke predictor for reducing perceived latency
|
|
21
|
+
*
|
|
22
|
+
* Estimates the next pen position based on recent velocity,
|
|
23
|
+
* useful for showing a "preview" stroke ahead of the actual stabilized input.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const predictor = new StrokePredictor({ historySize: 4 })
|
|
28
|
+
*
|
|
29
|
+
* canvas.onpointermove = (e) => {
|
|
30
|
+
* const point = { x: e.clientX, y: e.clientY, timestamp: e.timeStamp }
|
|
31
|
+
* const stabilized = pointer.process(point)
|
|
32
|
+
*
|
|
33
|
+
* if (stabilized) {
|
|
34
|
+
* predictor.addPoint(stabilized)
|
|
35
|
+
*
|
|
36
|
+
* // Get predicted point 16ms ahead
|
|
37
|
+
* const predicted = predictor.predict(16)
|
|
38
|
+
* if (predicted) {
|
|
39
|
+
* drawPreview(predicted)
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare class StrokePredictor {
|
|
46
|
+
private config;
|
|
47
|
+
private history;
|
|
48
|
+
constructor(config?: PredictionConfig);
|
|
49
|
+
/**
|
|
50
|
+
* Add a point to the predictor's history
|
|
51
|
+
*/
|
|
52
|
+
addPoint(point: PointerPoint): void;
|
|
53
|
+
/**
|
|
54
|
+
* Predict the position at a given time ahead
|
|
55
|
+
*
|
|
56
|
+
* @param lookAheadMs Time in milliseconds to predict ahead
|
|
57
|
+
* @returns Predicted point, or null if prediction is not possible
|
|
58
|
+
*/
|
|
59
|
+
predict(lookAheadMs: number): PointerPoint | null;
|
|
60
|
+
/**
|
|
61
|
+
* Get predicted points for rendering a preview stroke
|
|
62
|
+
*
|
|
63
|
+
* @param count Number of predicted points to generate
|
|
64
|
+
* @param intervalMs Time interval between predicted points
|
|
65
|
+
* @returns Array of predicted points
|
|
66
|
+
*/
|
|
67
|
+
predictMultiple(count: number, intervalMs?: number): PointerPoint[];
|
|
68
|
+
/**
|
|
69
|
+
* Calculate weighted average velocity from history
|
|
70
|
+
* Recent points are weighted more heavily
|
|
71
|
+
*/
|
|
72
|
+
private calculateAverageVelocity;
|
|
73
|
+
/**
|
|
74
|
+
* Get the current average velocity
|
|
75
|
+
*/
|
|
76
|
+
getVelocity(): {
|
|
77
|
+
vx: number;
|
|
78
|
+
vy: number;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Get the current speed (magnitude of velocity)
|
|
82
|
+
*/
|
|
83
|
+
getSpeed(): number;
|
|
84
|
+
/**
|
|
85
|
+
* Clear prediction history
|
|
86
|
+
*/
|
|
87
|
+
reset(): void;
|
|
88
|
+
/**
|
|
89
|
+
* Get number of points in history
|
|
90
|
+
*/
|
|
91
|
+
get historyLength(): number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Create a stroke predictor
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* const predictor = createPredictor({ historySize: 4, maxPredictionMs: 32 })
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export declare function createPredictor(config?: PredictionConfig): StrokePredictor;
|
|
102
|
+
//# sourceMappingURL=prediction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prediction.d.ts","sourceRoot":"","sources":["../src/prediction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAQD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,OAAO,CAA0B;gBAE7B,MAAM,GAAE,gBAAqB;IAQzC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAsBnC;;;;;OAKG;IACH,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IAiCjD;;;;;;OAMG;IACH,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,GAAE,MAAU,GAAG,YAAY,EAAE;IAatE;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IAwBhC;;OAEG;IACH,WAAW,IAAI;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE;IAKzC;;OAEG;IACH,QAAQ,IAAI,MAAM;IAKlB;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,IAAI,aAAa,IAAI,MAAM,CAE1B;CACF;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,gBAAgB,GAAG,eAAe,CAE1E"}
|
package/dist/spline.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Point, PointerPoint } from './types';
|
|
2
|
+
export interface CatmullRomOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Tension parameter (0-1)
|
|
5
|
+
* - 0: Looser curves (Uniform Catmull-Rom)
|
|
6
|
+
* - 0.5: Standard Catmull-Rom (Centripetal)
|
|
7
|
+
* - 1: Tighter curves (Chordal)
|
|
8
|
+
* @default 0.5
|
|
9
|
+
*/
|
|
10
|
+
tension?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Number of interpolated points between each input point pair
|
|
13
|
+
* @default 10
|
|
14
|
+
*/
|
|
15
|
+
segmentDivisions?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Whether to interpolate pressure and tilt values
|
|
18
|
+
* @default true
|
|
19
|
+
*/
|
|
20
|
+
interpolateAttributes?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Catmull-Rom spline interpolation for smooth curve generation
|
|
24
|
+
*
|
|
25
|
+
* Creates smooth curves through a series of points using the Catmull-Rom
|
|
26
|
+
* spline algorithm. Useful for upsampling stroke data or generating
|
|
27
|
+
* smooth paths for rendering.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const points = stabilizer.finish()
|
|
32
|
+
* const smooth = interpolateCatmullRom(points, { segmentDivisions: 5 })
|
|
33
|
+
* // smooth now has ~5x more points with smooth interpolation
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function interpolateCatmullRom(points: PointerPoint[], options?: CatmullRomOptions): PointerPoint[];
|
|
37
|
+
/**
|
|
38
|
+
* Calculate the total arc length of a set of points
|
|
39
|
+
*/
|
|
40
|
+
export declare function calculateArcLength(points: Point[]): number;
|
|
41
|
+
/**
|
|
42
|
+
* Resample points at uniform arc length intervals
|
|
43
|
+
*
|
|
44
|
+
* Creates a new set of points where each point is at an equal distance
|
|
45
|
+
* along the curve. Useful for consistent stroke rendering.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const uniform = resampleByArcLength(points, 5) // 5px between points
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function resampleByArcLength(points: PointerPoint[], spacing: number): PointerPoint[];
|
|
53
|
+
//# sourceMappingURL=spline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spline.d.ts","sourceRoot":"","sources":["../src/spline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAElD,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAEzB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,YAAY,EAAE,EACtB,OAAO,GAAE,iBAAsB,GAC9B,YAAY,EAAE,CAqDhB;AAyHD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAY1D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,YAAY,EAAE,EACtB,OAAO,EAAE,MAAM,GACd,YAAY,EAAE,CA+ChB"}
|