Security News
GitHub Removes Malicious Pull Requests Targeting Open Source Repositories
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
perfect-freehand
Advanced tools
The perfect-freehand npm package is designed to generate smooth, natural-looking strokes from a series of input points, which is particularly useful for drawing applications. It provides a way to create freehand drawings that look more polished and professional by smoothing out the input data.
Generate Smooth Strokes
This feature allows you to generate a smooth stroke from a series of input points. The code sample demonstrates how to use the `getStroke` function from the perfect-freehand package to create a stroke with specific options for size, thinning, smoothing, and streamline.
const { getStroke } = require('perfect-freehand');
const points = [
[0, 0],
[1, 1],
[2, 2],
[3, 3],
[4, 4]
];
const options = {
size: 8,
thinning: 0.5,
smoothing: 0.5,
streamline: 0.5
};
const stroke = getStroke(points, options);
console.log(stroke);
Paper.js is an open-source vector graphics scripting framework that runs on top of the HTML5 Canvas. It provides a powerful API for creating and manipulating vector graphics, including freehand drawing. Compared to perfect-freehand, Paper.js offers a broader range of vector graphics functionalities but may not be as specialized in generating smooth freehand strokes.
Rough.js is a library that allows you to create graphics that appear hand-drawn. It can be used to create freehand-like drawings, but its primary focus is on creating rough, sketchy graphics rather than smooth strokes. Compared to perfect-freehand, Rough.js is more about the aesthetic of roughness rather than the smoothness of freehand strokes.
Fabric.js is a powerful and simple JavaScript HTML5 canvas library. It provides an interactive object model on top of the canvas element, which includes support for freehand drawing. While Fabric.js offers a wide range of canvas manipulation features, perfect-freehand is more focused on the quality and smoothness of the stroke itself.
Draw perfect pressure-sensitive freehand strokes.
🔗 Demo
npm install perfect-freehand
or
yarn add perfect-freehand
This package's default export is a function that:
[x, y]
import getStroke from 'perfect-freehand'
You may format your input points as array or an object. In both cases, the value for pressure is optional (it will default to .5
).
getStroke([
[0, 0, 0],
[10, 5, 0.5],
[20, 8, 0.3],
])
getStroke([
{ x: 0, y: 0, pressure: 0 },
{ x: 10, y: 5, pressure: 0.5 },
{ x: 20, y: 8, pressure: 0.3 },
])
The options object is optional, as are each of its properties.
Property | Type | Default | Description |
---|---|---|---|
size | number | 8 | The base size (diameter) of the stroke. |
thinning | number | .5 | The effect of pressure on the stroke's size. |
smoothing | number | .5 | How much to soften the stroke's edges. |
streamline | number | .5 | How much to streamline the stroke. |
simulatePressure | boolean | true | Whether to simulate pressure based on velocity. |
easing | function | t => t | An easing function to apply to each point's pressure. |
start | function | t => t | Tapering options for the start of the line. |
end | { } | Tapering options for the end of the line. | |
last | boolean | false | Whether the stroke is complete. |
The start
and end
options accept an object:
Property | Type | Default | Description |
---|---|---|---|
taper | boolean | 0 | The distance to taper. |
easing | function | t => | An easing function for the tapering effect. |
When taper
is zero for either start or end, the library will add a rounded cap at that end of the line.
getStroke(myPoints, {
size: 8,
thinning: 0.5,
smoothing: 0.5,
streamline: 0.5,
easing: t => t * t * t,
simulatePressure: true,
last: true,
start: {
taper: 20,
easing: t => t * t * t,
},
end: {
taper: 20,
easing: t => t * t * t,
},
})
Tip: To create a stroke with a steady line, set the
thinning
option to0
.
Tip: To create a stroke that gets thinner with pressure instead of thicker, use a negative number for the
thinning
option.
While getStroke
returns an array of points representing a stroke, it's up to you to decide how you will render the stroke. The library does not export any rendering solutions.
For example, the function below will turn a stroke into SVG path data for use with either SVG paths or HTML Canvas (using the Path2D
constructor).
// Create SVG path data using the points from perfect-freehand.
function getSvgPathFromStroke(points) {
const d = []
if (stroke.length < 3) {
return ''
}
let p0 = stroke[stroke.length - 3]
let p1 = stroke[stroke.length - 2]
d.push('M', p0[0], p0[1], 'Q')
for (let i = 0; i < stroke.length; i++) {
d.push(p0[0], p0[1], (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2)
p0 = p1
p1 = stroke[i]
}
d.push('Z')
return d.join(' ')
}
To render a stroke as a flat polygon, add the polygon-clipping
package and use the following function together with the getSvgPathFromStroke
.
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.
import * as React from 'react'
import getStroke from 'perfect-freehand'
import { getSvgPathFromStroke } from './utils'
export default function Example() {
const [currentMark, setCurrentMark] = React.useState()
function handlePointerDown(e) {
e.preventDefault()
setCurrentMark({
type: e.pointerType,
points: [[e.pageX, e.pageY, e.pressure]],
})
}
function handlePointerMove(e) {
e.preventDefault()
if (e.buttons === 1) {
setCurrentMark({
...currentMark,
points: [...currentMark.points, [e.pageX, e.pageY, e.pressure]],
})
}
}
return (
<svg
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
style={{ touchAction: 'none' }}
>
{currentMark && (
<path
d={getSvgPathFromStroke(
getStroke(currentMark.points, {
size: 24,
thinning: 0.75,
smoothing: 0.5,
streamline: 0.5,
simulatePressure: currentMark.type !== 'pen',
})
)}
/>
)}
</svg>
)
}
StrokeOptions
A TypeScript type for the options object.
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.
getStrokePoints
const strokePoints = getStrokePoints(rawInputPoints)
Accepts an array of points (formatted either as [x, y, pressure]
or { x: number, y: number, pressure: number}
) and a streamline value. Returns a set of streamlined points as [x, y, pressure, angle, distance, lengthAtPoint]
. The path's total length will be the length of the last point in the array.
getStrokeOutlinePoints
Accepts an array of points (formatted as [x, y, pressure, angle, distance, length]
, i.e. the output of getStrokePoints
) and returns an array of points ([x, y]
) defining the outline of a pressure-sensitive stroke.
const outlinePoints = getOutlinePoints(strokePoints)
Please open an issue for support.
Have an idea or casual question? Visit the discussion page.
FAQs
Draw perfect pressure-sensitive freehand strokes.
The npm package perfect-freehand receives a total of 0 weekly downloads. As such, perfect-freehand popularity was classified as not popular.
We found that perfect-freehand demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.