Draw perfect freehand lines—in Flutter.

Overview

Screenshot

Draw perfect pressure-sensitive freehand lines.

🔗 A port of the perfect-freehand JavaScript library. Try out that demo.

💕 Love this library? Consider becoming a sponsor.

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.

Screenshot

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. It can be used with or without Flutter.

With Dart:

dart pub add perfect_freehand

With Flutter:

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<Point> myPoints = [
  Point(0, 0),
  Point(1, 2),
  // etc...
];

final stroke = getStroke(myPoints);

You may also provide options as named parameters:

final stroke = getStroke(
  myPoints,
  size: 16,
  thinning: 0.7,
  smoothing: 0.5,
  streamline: 0.5,
  taperStart: 0.0,
  taperEnd: 0.0,
  capStart: true,
  capEnd: true,
  simulatePressure: true,
  isComplete: false,
);

To use real pressure, provide each point's pressure as a third parameter.

List<Point> myPoints = [
  Point(0, 0, 0.2),
  Point(1, 2, 0.3),
  Point(2, 4, 0.4),
  // etc...
];

final stroke = getStroke(myPoints, simulatePressure: false);

Options

The optional parameters are:

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.
startTaper double 0 How far to taper the start of the line.
endTaper double 0 How far to taper the end of the line.
isComplete boolean true Whether the stroke is complete.
simulatePressure boolean true Whether to simulate pressure based on distance between points, or else use the provided Points' pressures.

Note: When the last property is true, the line's end will be drawn at the last input point, rather than slightly behind it.

Note: The cap property has no effect when taper is more than zero.

Tip: To create a stroke with a steady line, set the thinning option to 0.

Tip: To create a stroke that gets thinner with pressure instead of thicker, use a negative number for the thinning option.

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. Check the example project to see how you might draw these points in Flutter using a CustomPainter.

import 'package:flutter/material.dart';
import 'package:perfect_freehand/perfect_freehand.dart';

class StrokePainter extends CustomPainter {
  final List<Point> points;

  StrokePainter({ required this.points });

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint() ..color = Colors.black;

    // 1. Get the outline points from the input points
    final outlinePoints = getStroke(points);

    // 2. Render the points as a path
    final path = Path();

    if (outlinePoints.isEmpty) {
      // If the list is empty, don't do anything.
      return;
    } else if (outlinePoints.length < 2) {
      // If the list only has one point, draw a dot.
      path.addOval(Rect.fromCircle(
          center: Offset(outlinePoints[0].x, outlinePoints[0].y), radius: 1));
    } else {
      // Otherwise, draw a line that connects each point with a bezier curve segment.
      path.moveTo(outlinePoints[0].x, outlinePoints[0].y);

      for (int i = 1; i < outlinePoints.length - 1; ++i) {
        final p0 = outlinePoints[i];
        final p1 = outlinePoints[i + 1];
        path.quadraticBezierTo(
            p0.x, p0.y, (p0.x + p1.x) / 2, (p0.y + p1.y) / 2);
      }
    }

    // 3. Draw the path to the canvas
    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(StrokePainter oldDelegate) {
    return true;
  }
}

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 Points 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<Point> myPoints = [
  Point(0, 0),
  Point(1, 2),
  // etc...
];

final strokePoints = getStrokePoints(myPoints, size: 16);

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<Point> myPoints = [
  Point(0, 0),
  Point(1, 2),
  // etc...
];

final myStrokePoints = getStrokePoints(myPoints, size: 16);

final myOutlinePoints = getStrokeOutlinePoints(myStrokePoints, size: 16)

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.

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. 💰

Author

Comments
  • Implemented stylus pressure in radius calculation, StrokePoint generation, and example app (Resolves #5)

    Implemented stylus pressure in radius calculation, StrokePoint generation, and example app (Resolves #5)

    Implemented stylus pressure in radius calculation, StrokePoint generation, and example app (Resolves #5)

    The calculation behavior wasn't considering pressure when calculating the radius, nor interpolating the points. This PR adds that.

    Also, the example app was using a GestureDetector, which doesn't report pressure. I switch it to a Listener, which reports the kind of device, and in the case of a stylus, includes pressure.

    I also updated the .gitignore to match a standard Flutter project.

    opened by matthew-carroll 2
  • Various changes

    Various changes

    Hello Steve, Thank you for porting perfect freehand to dart. I was curious what kind of dart and flutter code someone with programming experience, but new to Dart, would write.

    You said on twitter that you are open to feedback, so here are some suggestions.

    The most important changes in this PR are the following:

    • extracted dart only logic into its own package (FYI you could use dart instead of typescript for deploying a js version of perfect freehand, see: https://dart.dev/tools/dart2js)
    • replaced streamcontrollers with setState (they are not needed in this case, setState is enough to notify the UI to repaint)
    • made some models const-able (the linter is able to highlight constructor calls that can be made constant. This will be more efficient in Dart as such models will only have to be allocated once.)
    • removed the set invocation from some setState calls which look like closures but aren't closures (closures don't need an arrow (abc) {} / function expressions don't need curly braces (abc) => ...

    Some of the more subjective changes are:

    • removed newlines for clarity
    • added trailing commas to help dartfmt
    • replaced ternary operators with ifelses for clarity
    • sorted methods in the main widget topologically which is more idiomatic in dart than a reverse topological sort.

    FYI: Flutter supports reading out various information from pressure sensitive input devices such as the Apple Pen on iPads.

    Thanks again :)

    opened by modulovalue 2
  • Time before the path begins

    Time before the path begins

    I'm noticing on my iPad with an Apple Pencil that there's about a second of contact with the screen before a path appears. This makes it difficult to make quick marks, such as crossing a "t" in a name signature.

    Is this delay part of perfect_freehand, or is it part of Flutter and/or the platform?

    If this is a part of perfect_freehand, is there a setting I can configure to start the path sooner?

    When I start to drag, I see a thin grey path following the Pencil tip before the perfect_freehand path begins. I think that grey path is coming from iOS, because I don't think I coded that behavior. But the point is, it seems like Pencil path information is available as soon as the user drags it on the screen, but there's a delay before the perfect_freehand path begins.

    opened by matthew-carroll 1
  • simulatePressure:false with pressure given in the Point is not working

    simulatePressure:false with pressure given in the Point is not working

    Hello Steve,

    Congratulations on the great library you have provided for drawing. We were completely taken up by the demo which is in Javascript. However, our application is in Flutter/Dart. We found that the example code and the code does not use pressure in generating the outline. If we pass the pressure from the pointer events to the third parameter in the Point and set the simulatePressure to false in the options, there is no effect of the pressure.

    We are using XP_Pen Deco 3 for drawing. The same XP-PEN works great on the web demo hosted on https://perfect-freehand-example.vercel.app/

    Best regards,

    Amit

    opened by AmitKoMail 1
  • Issues if canvas is scaled

    Issues if canvas is scaled

    If you have a scaled canvas, there are issues. brave_Hc8vpBigBv see https://butterfly-lhuv8bd8w-linwood.vercel.app/ (click on the pen painter, enable "zoom dependent" and zoom in a lot). grafik

    See here: https://github.com/LinwoodCloud/Butterfly/blob/c97a86e8080ea62183033abeae1b48011c0b9a3f/app/lib/renderers/elements/path.dart#L50

    opened by CodeDoctorDE 3
  • Export image & fixed support for stylus with pressure.

    Export image & fixed support for stylus with pressure.

    1. Added a button for exporting images (and png) in example app.

    2. Applied changes from issue https://github.com/steveruizok/perfect-freehand-dart/issues/4 . Thanks to @ercgeek. Tested with Apple Pencil and Samsung Galaxy Tab A. I couldn't make ForcePressGestureRecognizer to work so I used Listener instead in example app.

    3. Added some features I needed:

    • A listener that triggers when drawing outside the canvas
    • A flag to block normal touches after starting a drawing with a stylus.
    opened by elaget 1
  • Pressure processing when simulatePressure = false & isComplete = false

    Pressure processing when simulatePressure = false & isComplete = false

    First big thanks for publishing this.

    Using the library to render strokes passing on iPad stylus pressure, the stroke is incorrectly rendered with a constant pressure, whatever is passed on points[0].

    I think I found the issue on the use of lrp() to smooth out the points. Seems lrp() returns the pressure of the first point and not the interpolation of the pressures from both points.

    Changed lib\src\get_stroke_points.dart as follows and seems to corrects the issue. Using the pressure from the second point, not an interpolated value.

    // Original code
    // if (isComplete && i == pts.length - 1) {
    //   point = pts[i];
    // } else {
    //   point = lrp(prev.point, pts[i], t);
    // }
    
    // New code
    if (isComplete && i == pts.length - 1) {
      point = pts[i];
    } else {
      final tempPoint = lrp(prev.point, pts[i], t);
      point = Point(tempPoint.x, tempPoint.y, pts[i].p);
    }
    

    I'm not a dart expert, please let me know if I'm missing something here.

    opened by ercgeek 1
Owner
Steve Ruiz
Design and dev for creative tools.
Steve Ruiz
A collection of pixel-perfect iOS-styled components and properties for Flutter, following the official guidelines.

cupertinew ⚠️ Experimental and work in progress ⚠️ A collection of pixel-perfect iOS-styled components and properties for Flutter, following the offic

Jeroen Meijer (Jay) 30 Nov 10, 2022
1000+ Pixel-perfect svg unicons for your next flutter project

flutter_unicons 1000+ Pixel-perfect svg unicons for your next flutter project ispired by Unicons and vue-unicons Demo Download the gallery here. Insta

charlot Tabade 23 Oct 9, 2022
This perfect starter kit is an app based on React Native and UI Kitten library with Light and Dark themes support.

Kitten Tricks This perfect starter kit is an app based on React Native and UI Kitten library with Light and Dark themes support. It’s completely free

Akveo 7k Dec 30, 2022
A pixel perfect dashboard for mobile, tablet and desktop.

A pixel perfect dashboard for mobile, tablet and desktop.

BotCoder 25 Dec 27, 2022
Flutter plugin to simply integrate Agora Video Calling or Live Video Streaming to your app with just a few lines of code.

Agora UI Kit for Flutter Instantly integrate Agora video calling or video streaming into your Flutter application. Getting started Requirements An Ago

Agora.io Community 106 Dec 16, 2022
A Flutter widget that simply balances the lines of two-line text

Flutter Balanced Text ⚖️ A Flutter widget that simply balances the lines of two-line text, especially useful on long titles or short descriptions. Doe

Raşit Ayaz 3 Nov 10, 2022
Vineet Kalghatgi 32 May 13, 2022
Call Kit is a prebuilt feature-rich call component, which enables you to build one-on-one and group voice/video calls into your app with only a few lines of code.

Call Kit (ZegoUIKitPrebuiltCall) Call Kit is a prebuilt feature-rich call component, which enables you to build one-on-one and group voice/video calls

ZEGOCLOUD 9 Dec 26, 2022
Sample Flutter Drawing App which allows the user to draw onto the canvas along with color picker and brush thickness slider.

DrawApp Sample Flutter Drawing App which allows the user to draw onto the canvas along with color picker and brush thickness slider. All code free to

Jake Gough 226 Nov 3, 2022
A flutter plugin to draw the coordinates on the widget and as well as to find the given point is inside a list of coordinates or not.

Draw On A flutter plugin to draw the coordinates on widget and as well as to find the given point is inside a list of coordinates or not. For Draw on

Sivaramsiva10 4 Apr 5, 2022
Turtle graphics for Flutter. It simply uses a custom painter to draw graphics by a series of Logo-like commands.

flutter_turtle flutter_turtle is a simple implementation of turtle graphics for Flutter. It simply uses a custom painter to draw graphics by a series

Weizhong Yang a.k.a zonble 46 Dec 16, 2022
Grad text package - A Flutter Widget to draw gradients into text

grad_text A Flutter Widget to draw gradients into text.(Null safe) Demo Install

Karthik Sunil K 3 Jan 31, 2022
Doddle - A Flutter app where you can draw symmetrical drawing

doddle - (still under development) Amazing magical doodle game provide a creativ

Naser 53 Dec 28, 2022
A #Flutter package that let you draw a flow chart diagram with different kind of customizable elements

Flutter Flow Chart A package that let you draw a flow chart diagram with different kind of customizable elements. Dashboards can be saved for later us

Marco Bavagnoli 50 Jan 1, 2023
A CustomPaint example where we can draw with different colors and different stroke sizes

CustomPaint A CustomPaint example where we can draw with different colors and different stroke sizes. A Flutter application which runs on iOS, Android

Behruz Hurramov 0 Dec 27, 2021
🆙🚀 Flutter application upgrade/ Flutter App Upgrade /Flutter App Update/Flutter Update / download Plug-in

???? Flutter application upgrade/ Flutter App Upgrade /Flutter App Update/Flutter Update / download Plug-in (with notice bar progress), supports full upgrade, hot update and incremental upgrade

PengHui Li 344 Dec 30, 2022
ABC of Flutter widgets. Intended for super beginners at Flutter. Play with 35+ examples in DartPad directly and get familiar with various basic widgets in Flutter

Basic Widgets Examples This is aimed for complete beginners in Flutter, to get them acquainted with the various basic widgets in Flutter. Run this pro

Pooja Bhaumik 815 Jan 3, 2023
Minha primeira aplicação android utilizando Flutter feito no curso de Flutter da Cod3r Cursos Online. O foco dessa aplicação foi um contato inicial com o Flutter.

expenses Expenses é uma aplicação android simples feita em Flutter para controlar despesas pessoais. A aplicação consiste em: Listar transações feitas

Guilherme Teixeira Ais 2 Apr 19, 2022
Flutter Github Following Application, Using Flutter Provider and Flutter HTTP to get data from Github API.

Flutter Github Following Application Watch it on Youtube Previous Designs Checkout my Youtube channel Installation Please remember, after cloning this

Mohammad Rahmani 110 Dec 23, 2022