@stroke-stabilizer/core 0.1.3 → 0.1.4
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/dist/index.cjs +4 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/presets.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -954,11 +954,12 @@ function createStabilizedPointer(level) {
|
|
|
954
954
|
if (clampedLevel === 0) {
|
|
955
955
|
return pointer;
|
|
956
956
|
}
|
|
957
|
-
const
|
|
957
|
+
const t = Math.min(clampedLevel / 4, 1);
|
|
958
|
+
const minDistance = 1 + t * 2;
|
|
958
959
|
pointer.addFilter(noiseFilter({ minDistance }));
|
|
959
960
|
if (clampedLevel >= 21) {
|
|
960
|
-
const processNoise = 0.12 -
|
|
961
|
-
const measurementNoise = 0.4 +
|
|
961
|
+
const processNoise = 0.12 - t * 0.08;
|
|
962
|
+
const measurementNoise = 0.4 + t * 0.6;
|
|
962
963
|
pointer.addFilter(kalmanFilter({ processNoise, measurementNoise }));
|
|
963
964
|
}
|
|
964
965
|
if (clampedLevel >= 41) {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","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 * 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 // Batch processing fields\n private batchConfig: BatchConfig | null = null\n private pendingPoints: PointerPoint[] = []\n private rafId: number | null = null\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 // 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 // Level 1-100: Noise filter\n const minDistance = 1.0 + (clampedLevel / 100) * 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 - (clampedLevel / 100) * 0.08 // 0.12-0.04\n const measurementNoise = 0.4 + (clampedLevel / 100) * 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.cjs","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 * 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 // Batch processing fields\n private batchConfig: BatchConfig | null = null\n private pendingPoints: PointerPoint[] = []\n private rafId: number | null = null\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 // 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'\r\nimport { noiseFilter } from './filters/NoiseFilter'\r\nimport { kalmanFilter } from './filters/KalmanFilter'\r\nimport { movingAverageFilter } from './filters/MovingAverageFilter'\r\nimport { stringFilter } from './filters/StringFilter'\r\n\r\n/**\r\n * Create a StabilizedPointer based on level (0-100)\r\n *\r\n * Filter configuration by level:\r\n * - 0%: No stabilization\r\n * - 1-20%: Noise filter only\r\n * - 21-40%: Noise + Kalman\r\n * - 41-60%: Noise + Kalman + Moving average\r\n * - 61-80%: Above + String (light)\r\n * - 81-100%: Above + String (strong)\r\n *\r\n * @example\r\n * ```ts\r\n * // Medium stabilization\r\n * const pointer = createStabilizedPointer(50)\r\n *\r\n * // No stabilization\r\n * const raw = createStabilizedPointer(0)\r\n * ```\r\n */\r\nexport function createStabilizedPointer(level: number): StabilizedPointer {\r\n const clampedLevel = Math.max(0, Math.min(100, level))\r\n const pointer = new StabilizedPointer()\r\n\r\n if (clampedLevel === 0) {\r\n return pointer\r\n }\r\n\r\n // Scale: level 4 = max effect (t=1.0), level 100 = same as level 4\r\n const t = Math.min(clampedLevel / 4, 1.0)\r\n\r\n // Level 1-100: Noise filter\r\n const minDistance = 1.0 + t * 2.0 // 1.0-3.0\r\n pointer.addFilter(noiseFilter({ minDistance }))\r\n\r\n if (clampedLevel >= 21) {\r\n // Level 21-100: Kalman filter\r\n const processNoise = 0.12 - t * 0.08 // 0.12-0.04\r\n const measurementNoise = 0.4 + t * 0.6 // 0.4-1.0\r\n pointer.addFilter(kalmanFilter({ processNoise, measurementNoise }))\r\n }\r\n\r\n if (clampedLevel >= 41) {\r\n // Level 41-100: Moving average filter\r\n const windowSize = clampedLevel >= 61 ? 7 : 5\r\n pointer.addFilter(movingAverageFilter({ windowSize }))\r\n }\r\n\r\n if (clampedLevel >= 61) {\r\n // Level 61-100: String stabilization\r\n const stringLength = clampedLevel >= 81 ? 15 : 8\r\n pointer.addFilter(stringFilter({ stringLength }))\r\n }\r\n\r\n return pointer\r\n}\r\n\r\n/**\r\n * Create StabilizedPointer from preset name\r\n */\r\nexport type PresetName = 'none' | 'light' | 'medium' | 'heavy' | 'extreme'\r\n\r\nconst presetLevels: Record<PresetName, number> = {\r\n none: 0,\r\n light: 20,\r\n medium: 50,\r\n heavy: 75,\r\n extreme: 100,\r\n}\r\n\r\n/**\r\n * Create StabilizedPointer from preset\r\n *\r\n * @example\r\n * ```ts\r\n * const pointer = createFromPreset('medium')\r\n * ```\r\n */\r\nexport function createFromPreset(preset: PresetName): StabilizedPointer {\r\n return createStabilizedPointer(presetLevels[preset])\r\n}\r\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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"}
|
package/dist/index.js
CHANGED
|
@@ -952,11 +952,12 @@ function createStabilizedPointer(level) {
|
|
|
952
952
|
if (clampedLevel === 0) {
|
|
953
953
|
return pointer;
|
|
954
954
|
}
|
|
955
|
-
const
|
|
955
|
+
const t = Math.min(clampedLevel / 4, 1);
|
|
956
|
+
const minDistance = 1 + t * 2;
|
|
956
957
|
pointer.addFilter(noiseFilter({ minDistance }));
|
|
957
958
|
if (clampedLevel >= 21) {
|
|
958
|
-
const processNoise = 0.12 -
|
|
959
|
-
const measurementNoise = 0.4 +
|
|
959
|
+
const processNoise = 0.12 - t * 0.08;
|
|
960
|
+
const measurementNoise = 0.4 + t * 0.6;
|
|
960
961
|
pointer.addFilter(kalmanFilter({ processNoise, measurementNoise }));
|
|
961
962
|
}
|
|
962
963
|
if (clampedLevel >= 41) {
|
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 * 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 // Batch processing fields\n private batchConfig: BatchConfig | null = null\n private pendingPoints: PointerPoint[] = []\n private rafId: number | null = null\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 // 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 // Level 1-100: Noise filter\n const minDistance = 1.0 + (clampedLevel / 100) * 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 - (clampedLevel / 100) * 0.08 // 0.12-0.04\n const measurementNoise = 0.4 + (clampedLevel / 100) * 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/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 * 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 // Batch processing fields\n private batchConfig: BatchConfig | null = null\n private pendingPoints: PointerPoint[] = []\n private rafId: number | null = null\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 // 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'\r\nimport { noiseFilter } from './filters/NoiseFilter'\r\nimport { kalmanFilter } from './filters/KalmanFilter'\r\nimport { movingAverageFilter } from './filters/MovingAverageFilter'\r\nimport { stringFilter } from './filters/StringFilter'\r\n\r\n/**\r\n * Create a StabilizedPointer based on level (0-100)\r\n *\r\n * Filter configuration by level:\r\n * - 0%: No stabilization\r\n * - 1-20%: Noise filter only\r\n * - 21-40%: Noise + Kalman\r\n * - 41-60%: Noise + Kalman + Moving average\r\n * - 61-80%: Above + String (light)\r\n * - 81-100%: Above + String (strong)\r\n *\r\n * @example\r\n * ```ts\r\n * // Medium stabilization\r\n * const pointer = createStabilizedPointer(50)\r\n *\r\n * // No stabilization\r\n * const raw = createStabilizedPointer(0)\r\n * ```\r\n */\r\nexport function createStabilizedPointer(level: number): StabilizedPointer {\r\n const clampedLevel = Math.max(0, Math.min(100, level))\r\n const pointer = new StabilizedPointer()\r\n\r\n if (clampedLevel === 0) {\r\n return pointer\r\n }\r\n\r\n // Scale: level 4 = max effect (t=1.0), level 100 = same as level 4\r\n const t = Math.min(clampedLevel / 4, 1.0)\r\n\r\n // Level 1-100: Noise filter\r\n const minDistance = 1.0 + t * 2.0 // 1.0-3.0\r\n pointer.addFilter(noiseFilter({ minDistance }))\r\n\r\n if (clampedLevel >= 21) {\r\n // Level 21-100: Kalman filter\r\n const processNoise = 0.12 - t * 0.08 // 0.12-0.04\r\n const measurementNoise = 0.4 + t * 0.6 // 0.4-1.0\r\n pointer.addFilter(kalmanFilter({ processNoise, measurementNoise }))\r\n }\r\n\r\n if (clampedLevel >= 41) {\r\n // Level 41-100: Moving average filter\r\n const windowSize = clampedLevel >= 61 ? 7 : 5\r\n pointer.addFilter(movingAverageFilter({ windowSize }))\r\n }\r\n\r\n if (clampedLevel >= 61) {\r\n // Level 61-100: String stabilization\r\n const stringLength = clampedLevel >= 81 ? 15 : 8\r\n pointer.addFilter(stringFilter({ stringLength }))\r\n }\r\n\r\n return pointer\r\n}\r\n\r\n/**\r\n * Create StabilizedPointer from preset name\r\n */\r\nexport type PresetName = 'none' | 'light' | 'medium' | 'heavy' | 'extreme'\r\n\r\nconst presetLevels: Record<PresetName, number> = {\r\n none: 0,\r\n light: 20,\r\n medium: 50,\r\n heavy: 75,\r\n extreme: 100,\r\n}\r\n\r\n/**\r\n * Create StabilizedPointer from preset\r\n *\r\n * @example\r\n * ```ts\r\n * const pointer = createFromPreset('medium')\r\n * ```\r\n */\r\nexport function createFromPreset(preset: PresetName): StabilizedPointer {\r\n return createStabilizedPointer(presetLevels[preset])\r\n}\r\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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"}
|
package/dist/presets.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../src/presets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAMvD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,
|
|
1
|
+
{"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../src/presets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAMvD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAmCxE;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAA;AAU1E;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,iBAAiB,CAEtE"}
|