perfect-freehand
Advanced tools
Comparing version 0.3.3 to 0.3.4
@@ -1,14 +0,18 @@ | ||
# 0.3.4 | ||
## 0.3.4 | ||
- Fixes bug in short strokes. | ||
## 0.3.3 | ||
- Adds the `easing` property. This property accepts an [easing function](https://gist.github.com/gre/1650294) that will apply to all pressure measurements (real or simulated). Defaults to a linear easing (`t => t`). | ||
# 0.3.2 | ||
## 0.3.2 | ||
- Superficial changes. | ||
# 0.3.1 | ||
## 0.3.1 | ||
- Improves sharp corners that aren't sharp enough for caps, but are still sharp enough to confuse the distance-checking part of the algorithm. | ||
# 0.3.0 | ||
## 0.3.0 | ||
@@ -34,3 +38,3 @@ This version has breaking changes. | ||
# 0.2.5 | ||
## 0.2.5 | ||
@@ -40,19 +44,19 @@ - Improves caps for start and end. | ||
# 0.2.4 | ||
## 0.2.4 | ||
- Improves sharp corners. | ||
# 0.2.3 | ||
## 0.2.3 | ||
- Brings back `simulatePressure` until I have a better way of guessing. | ||
# 0.2.2 | ||
## 0.2.2 | ||
- Slight fix to line starts. | ||
# 0.2.1 | ||
## 0.2.1 | ||
- Fixes actual pressure sensitivity. | ||
# 0.2.0 | ||
## 0.2.0 | ||
@@ -70,16 +74,16 @@ - Breaks up algorithm into smaller functions. Because `getPath` returns an SVG path data, you can use `getPath` only with the Path2D element (for HTML Canvas) or SVG paths. These new functions will allow you to create paths in other rendering technologies. | ||
# 0.1.3 | ||
## 0.1.3 | ||
- Removes hidden options, uses `maxSize` for velocity calculations. | ||
# 0.1.2 | ||
## 0.1.2 | ||
- Fixes bug in pressure. | ||
# 0.1.2 | ||
## 0.1.2 | ||
- Fixes bug with empty input array. | ||
# 0.1.0 | ||
## 0.1.0 | ||
- Hey world. |
@@ -168,3 +168,3 @@ 'use strict'; | ||
if (len === 1 || totalLength <= 4) { | ||
if (len === 1 || totalLength <= size / 4) { | ||
var first = points[0], | ||
@@ -171,0 +171,0 @@ last = points[len - 1], |
@@ -1,2 +0,2 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var r=Math.hypot,t=Math.cos,n=Math.max,e=Math.min,i=Math.sin,u=Math.atan2,o=2*Math.PI;function a(r,t,n){return r*(1-n)+t*n}function s(r,n,e){return[t(n)*e+r[0],i(n)*e+r[1]]}function f(r,t){var n=(t-r)%o;return 2*n%o-n}function v(r,t,n){return r+f(r,t)*n}function h(r,t,n){return void 0===n&&(n=.5),[r[0]+(t[0]-r[0])*n,r[1]+(t[1]-r[1])*n]}function c(r,t){return u(t[1]-r[1],t[0]-r[0])}function p(t,n){return r(n[1]-t[1],n[0]-t[0])}function d(r,t,i){return n(t,e(i,r))}var l=Math.abs,M=Math.min,g=Math.PI,m=g/2,x=m,P=x/2;function y(r,t,n,e){return void 0===e&&(e=.5),void 0===t?r/2:(e=d(n(e),0,1),(t<0?a(r,r+r*d(t,-.95,-.05),e):a(r-r*d(t,.05,.95),r,e))/2)}function b(r,t){void 0===t&&(t=.5);var n=function(r){return Array.isArray(r[0])?r.map((function(r){var t=r[2];return[r[0],r[1],void 0===t?.5:t]})):r.map((function(r){var t=r.pressure;return[r.x,r.y,void 0===t?.5:t]}))}(r);if(0===n.length)return[];n[0]=[n[0][0],n[0][1],n[0][2]||.5,0,0,0];for(var e=1,i=n[e],u=n[0];e<n.length;i=n[++e],u=n[e-1])i[0]=a(u[0],i[0],1-t),i[1]=a(u[1],i[1],1-t),i[3]=c(i,u),i[4]=p(i,u),i[5]=u[5]+i[4];return n}function k(r,t){void 0===t&&(t={});var n=t.size,e=void 0===n?8:n,i=t.thinning,u=void 0===i?.5:i,o=t.smoothing,a=t.simulatePressure,d=void 0===a||a,b=t.easing,k=void 0===b?function(r){return r}:b,A=r.length,I=e*(void 0===o?.5:o),O=[],S=[],_=r[0],j=r[0],z=_,q=j,w=j[3],B=0,C=e/2,D=!0;if(0===A)return[];if(1===A||r[A-1][5]<=4){var E=r[0],F=r[A-1],G=c(E,F);u&&(C=y(e,u,k,F[2]));for(var H=0;H<=1;H+=.1)z=s(E,G+g+m-H*g,C),q=s(F,G+m-H*g,C),O.push(z),S.push(q);return O.concat(S)}for(var J=1;J<A;J++){var K=r[J-1],L=r[J],N=L[0],Q=L[1],R=L[2],T=L[3],U=L[4],V=L[5];if(u){if(d){var W=M(1-U/e,1),X=M(U/e,1);R=M(1,B+X/2*(W-B))}C=y(e,u,k,R)}if(D){if(V<e/4)continue;D=!1;for(var Y=0;Y<=1;Y+=.1)z=s(r[0],T+m-Y*g,C),O.push(z);q=s(r[0],T+m,C),S.push(q)}if(T=v(w,T,.75),J===A-1)for(var Z=0;Z<=1;Z+=.1)S.push(s([N,Q],T+m+Z*g,C));else{var $=f(K[3],T),rr=l($);if(rr>x&&V>C)for(var tr=h(K,[N,Q]),nr=0;nr<=1;nr+=.25)z=s(tr,w-m+nr*-g,C),q=s(tr,w+m+nr*g,C),O.push(z),S.push(q);else _=s([N,Q],T-m,C),j=s([N,Q],T+m,C),(rr>P||p(_,z)>I)&&(O.push(h(z,_)),z=_),(rr>P||p(j,q)>I)&&(S.push(h(q,j)),q=j);B=R,w=T}}return O.concat(S.reverse())}exports.default=function(r,t){return void 0===t&&(t={}),k(b(r,t.streamline),t)},exports.getStrokeOutlinePoints=k,exports.getStrokePoints=b; | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var r=Math.hypot,t=Math.cos,n=Math.max,e=Math.min,i=Math.sin,u=Math.atan2,o=2*Math.PI;function a(r,t,n){return r*(1-n)+t*n}function s(r,n,e){return[t(n)*e+r[0],i(n)*e+r[1]]}function f(r,t){var n=(t-r)%o;return 2*n%o-n}function v(r,t,n){return r+f(r,t)*n}function h(r,t,n){return void 0===n&&(n=.5),[r[0]+(t[0]-r[0])*n,r[1]+(t[1]-r[1])*n]}function c(r,t){return u(t[1]-r[1],t[0]-r[0])}function p(t,n){return r(n[1]-t[1],n[0]-t[0])}function d(r,t,i){return n(t,e(i,r))}var l=Math.abs,M=Math.min,g=Math.PI,m=g/2,x=m,P=x/2;function y(r,t,n,e){return void 0===e&&(e=.5),void 0===t?r/2:(e=d(n(e),0,1),(t<0?a(r,r+r*d(t,-.95,-.05),e):a(r-r*d(t,.05,.95),r,e))/2)}function b(r,t){void 0===t&&(t=.5);var n=function(r){return Array.isArray(r[0])?r.map((function(r){var t=r[2];return[r[0],r[1],void 0===t?.5:t]})):r.map((function(r){var t=r.pressure;return[r.x,r.y,void 0===t?.5:t]}))}(r);if(0===n.length)return[];n[0]=[n[0][0],n[0][1],n[0][2]||.5,0,0,0];for(var e=1,i=n[e],u=n[0];e<n.length;i=n[++e],u=n[e-1])i[0]=a(u[0],i[0],1-t),i[1]=a(u[1],i[1],1-t),i[3]=c(i,u),i[4]=p(i,u),i[5]=u[5]+i[4];return n}function k(r,t){void 0===t&&(t={});var n=t.size,e=void 0===n?8:n,i=t.thinning,u=void 0===i?.5:i,o=t.smoothing,a=t.simulatePressure,d=void 0===a||a,b=t.easing,k=void 0===b?function(r){return r}:b,A=r.length,I=e*(void 0===o?.5:o),O=[],S=[],_=r[0],j=r[0],z=_,q=j,w=j[3],B=0,C=e/2,D=!0;if(0===A)return[];if(1===A||r[A-1][5]<=e/4){var E=r[0],F=r[A-1],G=c(E,F);u&&(C=y(e,u,k,F[2]));for(var H=0;H<=1;H+=.1)z=s(E,G+g+m-H*g,C),q=s(F,G+m-H*g,C),O.push(z),S.push(q);return O.concat(S)}for(var J=1;J<A;J++){var K=r[J-1],L=r[J],N=L[0],Q=L[1],R=L[2],T=L[3],U=L[4],V=L[5];if(u){if(d){var W=M(1-U/e,1),X=M(U/e,1);R=M(1,B+X/2*(W-B))}C=y(e,u,k,R)}if(D){if(V<e/4)continue;D=!1;for(var Y=0;Y<=1;Y+=.1)z=s(r[0],T+m-Y*g,C),O.push(z);q=s(r[0],T+m,C),S.push(q)}if(T=v(w,T,.75),J===A-1)for(var Z=0;Z<=1;Z+=.1)S.push(s([N,Q],T+m+Z*g,C));else{var $=f(K[3],T),rr=l($);if(rr>x&&V>C)for(var tr=h(K,[N,Q]),nr=0;nr<=1;nr+=.25)z=s(tr,w-m+nr*-g,C),q=s(tr,w+m+nr*g,C),O.push(z),S.push(q);else _=s([N,Q],T-m,C),j=s([N,Q],T+m,C),(rr>P||p(_,z)>I)&&(O.push(h(z,_)),z=_),(rr>P||p(j,q)>I)&&(S.push(h(q,j)),q=j);B=R,w=T}}return O.concat(S.reverse())}exports.default=function(r,t){return void 0===t&&(t={}),k(b(r,t.streamline),t)},exports.getStrokeOutlinePoints=k,exports.getStrokePoints=b; | ||
//# sourceMappingURL=perfect-freehand.cjs.production.min.js.map |
@@ -164,3 +164,3 @@ var hypot = Math.hypot, | ||
if (len === 1 || totalLength <= 4) { | ||
if (len === 1 || totalLength <= size / 4) { | ||
var first = points[0], | ||
@@ -167,0 +167,0 @@ last = points[len - 1], |
{ | ||
"version": "0.3.3", | ||
"version": "0.3.4", | ||
"name": "perfect-freehand", | ||
@@ -4,0 +4,0 @@ "author": { |
134
README.md
@@ -1,9 +0,15 @@ | ||
# Perfect Freehand | ||
# ![Screenshot](screenshot.svg 'Perfect Freehand') | ||
Perfect freehand is a library for generating freehand strokes. | ||
Draw perfect pressure-sensitive freehand strokes. | ||
![Screenshot](https://github.com/steveruizok/perfect-freehand/raw/main/screenshot.png) | ||
🔗 [Demo](https://perfect-freehand-example.vercel.app/) | ||
## Table of Contents | ||
- [Installation](#installation) | ||
- [Usage](#usage) | ||
- [Support](#support) | ||
- [Discussion](#discussion) | ||
- [Author](#Author) | ||
## Installation | ||
@@ -23,3 +29,3 @@ | ||
This library's default export is a function that: | ||
This package's default export is a function that: | ||
@@ -81,19 +87,19 @@ - accepts an array of points and an (optional) options object | ||
For example, here is a function that takes in a stroke and returns SVG path data. You can use the string returned by this function in two ways. For SVG, you can pass the data into `path` element's [`d` property](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d). For HTML canvas, you can pass the string into the [`Path2D` constructor](https://developer.mozilla.org/en-US/docs/Web/API/Path2D/Path2D#using_svg_paths) and then stroke or fill the path. | ||
For example, the function below will turn a stroke into SVG path data for use with either [SVG paths](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d) or HTML Canvas (using the [`Path2D` constructor](https://developer.mozilla.org/en-US/docs/Web/API/Path2D/Path2D#using_svg_paths)). | ||
```js | ||
// Create SVG path data using the points from perfect-freehand. | ||
function getSvgPathFromStroke(stroke) { | ||
function getSvgPathFromStroke(points) { | ||
if (points.length === 0) return '' | ||
const d = [] | ||
let [p0, p1] = stroke | ||
let [p0, p1] = points | ||
d.push(`M ${p0[0]} ${p0[1]} Q`) | ||
d.push('M', p0[0], p0[1], 'Q') | ||
for (let i = 1; i < stroke.length; i++) { | ||
const mpx = p0[0] + (p1[0] - p0[0]) / 2 | ||
const mpy = p0[1] + (p1[1] - p0[1]) / 2 | ||
d.push(`${p0[0]},${p0[1]} ${mpx},${mpy}`) | ||
for (let i = 1; i < points.length; i++) { | ||
d.push(p0[0], p0[1], (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2) | ||
p0 = p1 | ||
p1 = stroke[i + 1] | ||
p1 = points[i] | ||
} | ||
@@ -107,4 +113,26 @@ | ||
# Example | ||
To render a stroke as a flat polygon, add the [`polygon-clipping`](https://github.com/mfogel/polygon-clipping) package and use the following function together with the `getSvgPathFromStroke`. | ||
```js | ||
import polygonClipping from 'polygon-clipping' | ||
function getFlatSvgPathFromStroke(stroke) { | ||
const poly = polygonClipping.union([stroke]) | ||
const d = [] | ||
for (let face of poly) { | ||
for (let points of face) { | ||
d.push(getSvgPathFromStroke(points)) | ||
} | ||
} | ||
return d.join(' ') | ||
} | ||
``` | ||
> **Tip:** For implementations in Typescript, see the example project included in this repository. | ||
### Example | ||
```jsx | ||
@@ -119,2 +147,3 @@ import * as React from 'react' | ||
function handlePointerDown(e) { | ||
e.preventDefault() | ||
setCurrentMark({ | ||
@@ -127,2 +156,3 @@ type: e.pointerType, | ||
function handlePointerMove(e) { | ||
e.preventDefault() | ||
if (e.buttons === 1) { | ||
@@ -136,17 +166,4 @@ setCurrentMark({ | ||
const stroke = currentMark | ||
? getStroke(currentMark.points, { | ||
size: 16, | ||
thinning: 0.75, | ||
smoothing: 0.5, | ||
streamline: 0.5, | ||
easing: t => t * t * t, | ||
simulatePressure: currentMark.type !== 'pen', | ||
}) | ||
: [] | ||
return ( | ||
<svg | ||
width={800} | ||
height={600} | ||
onPointerDown={handlePointerDown} | ||
@@ -156,3 +173,15 @@ onPointerMove={handlePointerMove} | ||
> | ||
{currentMark && <path d={getSvgPathFromStroke(stroke)} />} | ||
{currentMark && ( | ||
<path | ||
d={getSvgPathFromStroke( | ||
getStroke(currentMark.points, { | ||
size: 24, | ||
thinning: 0.75, | ||
smoothing: 0.5, | ||
streamline: 0.5, | ||
simulatePressure: currentMark.type !== 'pen', | ||
}) | ||
)} | ||
/> | ||
)} | ||
</svg> | ||
@@ -165,6 +194,12 @@ ) | ||
# Advanced Usage | ||
### Advanced Usage | ||
## Functions | ||
#### `StrokeOptions` | ||
A TypeScript type for the options object. | ||
```ts | ||
import { StrokeOptions } from 'perfect-freehand' | ||
``` | ||
For advanced usage, the library also exports smaller functions that `getStroke` uses to generate its SVG data. While you can use `getStroke`'s data to render strokes with an HTML canvas (via the Path2D element) or with SVG paths, these new functions will allow you to create paths in other rendering technologies. | ||
@@ -175,2 +210,3 @@ | ||
```js | ||
const strokePoints = getStrokePoints(rawInputPoints) | ||
``` | ||
@@ -184,40 +220,16 @@ | ||
## Rendering a Flattened Stroke | ||
To render a stroke as a flat polygon, add the `polygon-clipping` package and use (or refer to) the following function. | ||
```js | ||
import getStroke from 'perfect-freehand' | ||
import polygonClipping from 'polygon-clipping' | ||
const outlinePoints = getOutlinePoints(strokePoints) | ||
``` | ||
function getFlatSvgPathFromStroke(stroke) { | ||
## Support | ||
const poly = polygonClipping.union([stroke] as any) | ||
Please [open an issue](https://github.com/steveruizok/perfect-freehand/issues/new) for support. | ||
const d = [] | ||
## Discussion | ||
for (let face of poly) { | ||
for (let pts of face) { | ||
let [p0, p1] = pts | ||
Have an idea or casual question? Visit the [discussion page](https://github.com/steveruizok/perfect-freehand/discussions). | ||
d.push(`M ${p0[0]} ${p0[1]} Q`) | ||
for (let i = 1; i < pts.length; i++) { | ||
const mpx = p0[0] + (p1[0] - p0[0]) / 2 | ||
const mpy = p0[1] + (p1[1] - p0[1]) / 2 | ||
d.push(`${p0[0]},${p0[1]} ${mpx},${mpy}`) | ||
p0 = p1 | ||
p1 = pts[i + 1] | ||
} | ||
d.push('Z') | ||
} | ||
} | ||
return d.join(' ') | ||
} | ||
``` | ||
## Author | ||
- [@steveruizok](https://twitter.com/steveruizok) |
@@ -109,3 +109,3 @@ import { | ||
// If the point is only one point long, draw two caps at either end. | ||
if (len === 1 || totalLength <= 4) { | ||
if (len === 1 || totalLength <= size / 4) { | ||
let first = points[0], | ||
@@ -112,0 +112,0 @@ last = points[len - 1], |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
96012
226