steveruizok/perfect-freehand-dart
Draw perfect freehand lines—in Flutter.
前往 github 下载Draw perfect pressure-sensitive freehand lines.
🔗 A port of the perfect-freehand JavaScript library.
Try out the demo!
💕 Love this library? Consider becoming a sponsor for
steveruizok
(the author of the original JavaScript library) or
adil192
(the maintainer of the Dart package).
Table of Contents
Introduction
This package exports a function named getStroke
that will generate the points for a polygon based on an array of points.
To do this work, getStroke
first creates a set of spline points (red) based on the input points (grey) and then creates outline points (blue). You can render the result any way you like, using whichever technology you prefer.
Installation
This package is available on pub.dev
and requires Flutter.
If you’re using Dart without Flutter, check out versions 1.x.x
of this package.
flutter pub add perfect_freehand
See here for more.
Usage
This package exports a function named getStroke
that:
- accepts an array of points and several options
- returns a stroke outline as an array of points
import 'package:perfect_freehand/perfect_freehand.dart';
List<PointVector> myPoints = [
PointVector(0, 0),
PointVector(1, 2),
// etc...
];
final stroke = getStroke(myPoints);
You may also provide options to getStroke
.
You’ll most likely store the StrokeOptions
object in a variable,
but you can also pass it directly to getStroke
.
final stroke = getStroke(
myPoints,
options: StrokeOptions(
size: 16,
thinning: 0.7,
smoothing: 0.5,
streamline: 0.5,
simulatePressure: true,
start: StrokeEndOptions(
cap: true,
taperEnabled: true,
),
end: StrokeEndOptions(
cap: true,
taperEnabled: true,
),
isComplete: false,
),
);
To use real pressure, provide each point’s pressure as a third parameter.
List<PointVector> myPoints = [
PointVector(0, 0, 0.2),
PointVector(1, 2, 0.3),
PointVector(2, 4, 0.4),
// etc...
];
final stroke = getStroke(
myPoints,
options: StrokeOptions(
simulatePressure: false,
),
);
Options
You can customize the stroke by passing a StrokeOptions
object as the second parameter to getStroke
.
This object accepts the following properties:
Property | Type | Default | Description |
---|---|---|---|
size |
double | 16 | The base size (diameter) of the stroke. |
thinning |
double | .5 | The effect of pressure on the stroke’s size. |
smoothing |
double | .5 | How much to soften the stroke’s edges. |
streamline |
double | .5 | How much to remove variation from the input points. |
simulatePressure |
bool | true | Whether to simulate pressure based on distance between points, or else use the provided PointVectors’ pressures. |
isComplete |
bool | true | Whether the stroke is complete. |
start |
StrokeEndOptions | How far to taper the start of the line. | |
end |
StrokeEndOptions | How far to taper the end of the line. | |
start.cap |
bool | true | Whether to cap the start of the line. |
start.taperEnabled |
bool | false | Whether to taper the start of the line. |
start.customTaper |
double | null | A custom taper value for the start of the line, defaults to the total running length. |
Notes:
- When the
isComplete
property istrue
, the line’s end will be drawn at the last input point, rather than slightly behind it. - The
StrokeEndOptions.cap
property has no effect whenStrokeEndOptions.taperEnabled
istrue
. - To create a stroke with a constant width, set the
thinning
option to0
. - To create a stroke that gets thinner with pressure instead of thicker, use a negative number for the
thinning
option. - You can change the defaults by setting e.g.
StrokeOptions.defaultThinning
orStrokeEndOptions.defaultCap
to a different value.
Rendering
While getStroke
returns an array of points representing the outline of a stroke, it’s up to you to decide how you will render these points.
See the StrokePainter
class in the
example project
to see how you might draw these points in Flutter with a CustomPainter
.
Advanced Usage
For advanced usage, the library also exports smaller functions that getStroke
uses to generate its outline points.
getStrokePoints
A function that accepts an array of PointVector
s and returns a set of StrokePoints
. The path’s total length will be the runningLength
of the last point in the array. Like getStroke
, this function also accepts any of the optional named parameters listed above.
List<PointVector> myPoints = [
PointVector(0, 0),
PointVector(1, 2),
// etc...
];
final options = StrokeOptions(
size: 16,
);
final strokePoints = getStrokePoints(myPoints, options: options);
getOutlinePoints
A function that accepts an array of StrokePoints (i.e. the output of getStrokePoint
) and returns an array of Points defining the outline of a stroke. Like getStroke
, this function also accepts any of the optional named parameters listed above.
List<PointVector> myPoints = [
PointVector(0, 0),
PointVector(1, 2),
// etc...
];
final options = StrokeOptions(
size: 16,
);
final myStrokePoints = getStrokePoints(myPoints, options: options);
final myOutlinePoints = getStrokeOutlinePoints(myStrokePoints, options: options);
Note: Internally, the getStroke
function passes the result of getStrokePoints
to getStrokeOutlinePoints
, just as shown in this example. This means that, in this example, the result of myOutlinePoints
will be the same as if the myPoints
List had been passed to getStroke
.
rememberSimulatedPressure
Pressure simulation relies on the distance between points to determine the pressure at each point.
But in some scenarios, it would be wasteful to store all of these points.
The rememberSimulatedPressure
argument solves this problem by calculating the pressure once
and then storing it in the original array of points.
/// Input points without pressure values
List<PointVector> myPoints = [/* ... */];
getStroke(
myPoints,
options: StrokeOptions(
// isComplete and simulatePressure must be true for rememberSimulatedPressure
simulatePressure: true,
isComplete: true,
),
rememberSimulatedPressure: true,
);
// myPoints now have pressure values (except for duplicate points)
print(myPoints[0].pressure); // (some number, not null)
// We are now free to compress myPoints however we want, e.g.
// this function removes duplicate points and points that are too close together:
void optimisePoints({double thresholdMultiplier = 0.1}) {
if (points.length <= 3) return;
final minDistance = strokeOptions.size * thresholdMultiplier;
// Duplicate points have null pressure values, so we can remove them
myPoints.removeWhere((point) => point.pressure == null);
for (int i = 1; i < points.length - 1; i++) {
final point = points[i];
final prev = points[i - 1];
final next = points[i + 1];
if (prev.distanceSquaredTo(point) < minDistance * minDistance &&
next.distanceSquaredTo(point) < minDistance * minDistance) {
points.removeAt(i);
i--;
}
}
}
Community
Support
Need help? Please open an issue for support.
Discussion
Have an idea or casual question? Visit the discussion page.
License
- MIT
- …but if you’re using
perfect-freehand
in a commercial product,
consider becoming a sponsor for
steveruizok
(the author of the original JavaScript library) or
adil192
(the maintainer of the Dart package)… 💰
![](/images/banner-woo.jpg)
与 steveruizok/perfect-freehand-dart 相关优秀项目推荐下载
AppFlowy
47859
AppFlowy is an open-source alternative to Notion. You are in charge of your data and customizations. Built with Flutter and Rust.
localsend
33797
An open-source cross-platform alternative to AirDrop
spotube
23772
🎧 Open source Spotify client that doesn't require Premium nor uses Electron! Available for both desktop & mobile!
revanced-manager
15035
💊 Application to use ReVanced on Android
gsy_github_app_flutter
14559
Flutter 超完整的开源项目,功能丰富,适合学习和日常使用。GSYGithubApp系列的优势:我们目前已经拥有Flutter、Weex、ReactNative、kotlin 四个版本。 功能齐全,项目框架内技术涉及面广,完成度高,持续维护,配套文章,适合全面学习,对比参考。跨平台的开源Github客户端App,更好的体验,更丰富的功能,旨在更好的日常管理和维护个人Github,提供更好更方便的驾车体验Σ( ̄。 ̄ノ)ノ。同款Weex版本 : https://github.com/CarGuo/GSYGithubAppWeex 、同款React Native版本 : https://github.com/CarGuo/GSYGithubApp 、原生 kotlin 版本 https://github.com/CarGuo/GSYGithubAppKotlin
dio
12249
A powerful HTTP client for Dart and Flutter, which supports global settings, Interceptors, FormData, aborting and canceling a request, files uploading and downloading, requests timeout, custom adapters, etc.
gopeed
11832
A modern download manager that supports all platforms. Built with Golang and Flutter.
bloc
11441
A predictable state management library that helps implement the BLoC design pattern
getx
9899
Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with Get.
flame
8846
A Flutter based game engine.
flutter_deer
7595
🦌 Flutter 练习项目(包括集成测试、可访问性测试)。内含完整UI设计图,更贴近真实项目的练习。Flutter practice project (including integration testing and accessibility testing). Contains complete UI design drawings for a more realistic practice project.
fish-redux
7343
An assembled flutter application framework.
hiddify-next
7232
Multi-platform auto-proxy client, supporting Sing-box, X-ray, TUIC, Hysteria, Reality, Trojan, SSH etc. It’s an open-source, secure and ad-free.
ente
6686
Fully open source, End to End Encrypted alternative to Google Photos and Apple Photos
fl_chart
6451
FL Chart is a highly customizable Flutter chart library that supports Line Chart, Bar Chart, Pie Chart, Scatter Chart, and Radar Chart.
pixez-flutter
6360
一个支持免代理直连及查看动图的第三方Pixiv flutter客户端
Flutter-Responsive-Admin-Panel-or-Dashboard
6355
Responsive Admin Panel or Dashboard using Flutter
aidea
6021
AIdea 是一款支持 GPT 以及国产大语言模型通义千问、文心一言等,支持 Stable Diffusion 文生图、图生图、 SDXL1.0、超分辨率、图片上色的全能型 APP。
riverpod
5828
A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
pikapika
5463
美观易用且无广告的漫画和游戏客户端,同时支持MacOS,Windows,Android,iOS。