Scribble is a lightweight library for freehand drawing in Flutter supporting pressure, variable line width and more!

Related tags

Utilities scribble
Overview

Scribble

Scribble is a lightweight library for freehand drawing in Flutter supporting pressure, variable line width and more!

At the moment, scribble needs at least Flutter 2.5 to work

scribble_demo

Note: Scribble is still in development and will receive more features down the line!

Features

  • Variable line width
  • Pen and touch pressure support
  • Lines get slimmer when the pen is moved more quickly
  • Line eraser support
  • Full undo/redo support using history_state_notifier
  • Sketches are fully serializable

Pipeline

  • Load sketches
  • PNG export

Usage

You can find a full working example in the example directory

You can create a drawing surface by adding the Scribble widget to your widget tree and passing in a ScribbleNotifier.

Where you manage this notifier is up to you, but since it is a StateNotifier, it works amazingly with riverpod for example.

import 'package:flutter_riverpod/flutter_riverpod.dart';

final scribbleStateProvider =
StateNotifierProvider.autoDispose<ScribbleNotifier, ScribbleState>(
      (ref) => ScribbleNotifier(),
);

You can then pass the notifier to the scribble widget.

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

class App extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    return Scaffold(
      body: Scribble(
        notifier: watch(scribbleStateProvider.notifier),
      ),
    );
  }
}

Use the public methods on ScribbleNotifier to control the behavior (for example from a button in the UI:

// Set color
notifier.setColor(Colors.black);

// Clear
notifier.clear();

// Undo
notifier.undo();

//... 

Additional information

As mentioned above, the package is still under development, but we already use it in the app we are currently developing.

Feel free to contribute, or open issues in our GitHub repo.

Comments
  • perfect_freehand integration

    perfect_freehand integration

    Would you be open to using perfect_freehand to handle the stroke painting? I had a quick play today plugging perfect_freehand into ScribblePainter as part of an internal company hackathon, and the results were pretty good (much smoother lines). I'd be happy to put together a PoC PR if that was a direction you'd be interested in?

    opened by mattrussell-sonocent 6
  • Sketch is cut off on different screen sizes

    Sketch is cut off on different screen sizes

    I realized after I took the screenshots that I forgot a word so ignore that please lol.

    When a sketch is drawn on a full screen iPad and then the view is in split screen the sketch is cut off.

    IMG_0288 IMG_0290 IMG_0289

    I am using just the basic scribble widget

     Scribble(
      notifier: _notifier,
      drawPen: true,
    ),
    
    bug good first issue 
    opened by alexrabin 4
  • Binary encoder/decoder

    Binary encoder/decoder

    I have written a binary encoder/decoder for a sketch. This is because the json format which is provided per default consumes too much space for my project. If you are interested to include this encoder/decoder feel free to use it as you want.

    import 'dart:typed_data';
    
    import 'package:flutter/services.dart';
    import 'package:scribble/scribble.dart';
    
    /// Encodes a Sketch into its binary representation.
    ///
    /// Versions:
    /// 0001: Initial version
    ///
    /// Attention. The binary representation of the sketch is not 100% identical because the double values will be stored with a precision of 1/10th.
    class ScribbleEncoder {
      List<int> encode(Sketch sketch) {
        List<int> result = [];
    
        ByteData byteData = ByteData(8);
        // signature: cbbe (the hex-compatible characters of scribble) followed by the version number.
        byteData.setInt32(0, 0xcbbe0001);
        byteData.setInt32(4, sketch.lines.length);
        result.addAll(byteData.buffer.asUint8List());
    
        sketch.lines.forEach((element) {
          result.addAll(_encodeLine(element));
        });
        return result;
      }
    
      List<int> _encodeLine(SketchLine line) {
        List<int> result = [];
    
        ByteData byteData = ByteData(12);
        byteData.setInt32(0, line.color);
        byteData.setInt32(4, (line.width * 10).round());
        byteData.setInt32(8, line.points.length);
        result.addAll(byteData.buffer.asUint8List());
        //print("Encoding line with ${line.points.length} points");
        line.points.forEach((element) {
          result.addAll(_encodePoint(element));
        });
        return result;
      }
    
      ///
      /// Encodes one Point and returns the encoded bytes. Each point consumes 10 byte.
      List<int> _encodePoint(Point point) {
        ByteData result = ByteData(10);
        result.setInt32(0, (point.x * 10).round());
        result.setInt32(4, (point.y * 10).round());
        result.setInt16(8, (point.pressure * 10).round());
        return result.buffer.asUint8List();
      }
    }
    

    import 'dart:typed_data';
    
    import 'package:flutter/services.dart';
    import 'package:scribble/scribble.dart';
    
    class ScribbleDecoder {
      Sketch decode(List<int> data) {
        if (data.length < 8) throw Exception("Invalid length (${data.length}");
        if (data[0] != 0xcb || data[1] != 0xbe)
          throw Exception("Invalid signature");
        int version = data[2] * 0xff + data[3];
        if (version != 0x0001) throw Exception("Unknown version ${version}");
    
        ByteData byteData = ByteData.sublistView(Uint8List.fromList(data));
        int lineLength = byteData.getInt32(4);
        if (lineLength < 0) throw Exception("nbr of Lines invalid");
        int idx = 8;
        List<SketchLine> lines = [];
        for (int lineNbr = 0; lineNbr < lineLength; ++lineNbr) {
          _LineResult _lineResult = _decodeLine(byteData, idx);
          lines.add(_lineResult.line);
          idx = _lineResult.idx;
        }
        Sketch result = Sketch(lines: lines);
        return result;
      }
    
      _LineResult _decodeLine(ByteData byteData, int idx) {
        if (byteData.lengthInBytes < idx + 12)
          throw Exception(
              "Invalid length of sourcefile to read a line at position $idx");
        int color = byteData.getInt32(idx + 0);
        double width = byteData.getInt32(idx + 4) / 10;
        int pointLength = byteData.getInt32(idx + 8);
        if (pointLength < 0) throw Exception("nbr of Points invalid");
        //print("Decoding line with $pointLength points at index $idx");
        List<Point> points = [];
        idx += 12;
        for (int pointNbr = 0; pointNbr < pointLength; ++pointNbr) {
          Point point = _decodePoint(byteData, idx);
          points.add(point);
          idx += 10;
        }
        return _LineResult(
            SketchLine(points: points, color: color, width: width), idx);
      }
    
      Point _decodePoint(ByteData byteData, int idx) {
        if (byteData.lengthInBytes < idx + 10)
          throw Exception(
              "Invalid length of sourcefile to read a point at position $idx");
        double x = byteData.getInt32(idx + 0) / 10;
        double y = byteData.getInt32(idx + 4) / 10;
        double pressure = byteData.getInt16(idx + 8) / 10;
        return Point(x, y, pressure: pressure);
      }
    }
    
    /////////////////////////////////////////////////////////////////////////////
    
    class _LineResult {
      final SketchLine line;
      final int idx;
    
      _LineResult(this.line, this.idx);
    }
    
    opened by mikes222 4
  • Saving and pen detection

    Saving and pen detection

    Awesome library. Is there a way to save your drawing to edit at another time? Also, is there a way to make if only work if a pen or pencil is detected?

    Thank you!

    opened by Nightbl927 3
  • Problem when using part of the screen

    Problem when using part of the screen

    Hi, I have implemented the Scribble on part of the screen (inside a container) Although it does not allow to start paining when out of the bounds of the container, If, while drawing I go out of the boundries of the container, it continues drawing. ScribbleProblem01

    opened by YahalomsGuy 2
  • Create Scribble Viewer (View not edit)

    Create Scribble Viewer (View not edit)

    So I had a use case where I needed to just view a scribble sketch. As I started working on this solution I realized that there was a lot of extra space in my sketch (ie. you draw a smiley face in the center of the canvas). So I came up with some helper functions that helped me create a scribble viewer that crops the sketch where it removes the whitespace above and below the main sketch.

    These are the extensions I needed to help create the Sketch viewer.

    extension SketchExt on Sketch {
      Sketch croppedSketch() {
        var newLines = List<SketchLine>.empty(growable: true);
        double minY = double.maxFinite;
        for (var line in lines) {
          var temp = line.points
              .reduce((value, element) => value.y < element.y ? value : element);
          if (temp.y < minY) {
            minY = temp.y;
          }
        }
    
        newLines = lines.map((e) {
          var newE = e;
          List<Point> points = List<Point>.empty(growable: true);
          for (var p in e.points) {
            var newP = p.copyWith(y: p.y - minY);
            points.add(newP);
          }
          newE = newE.copyWith(points: points);
          return newE;
        }).toList();
        return copyWith(lines: newLines);
      }
    
      double getTotalHeight() {
        double minY = double.maxFinite;
        double maxY = -double.maxFinite;
        for (var line in lines) {
          var minTemp = line.points
              .reduce((value, element) => value.y < element.y ? value : element);
          var maxTemp = line.points
              .reduce((value, element) => value.y > element.y ? value : element);
          if (minTemp.y < minY) {
            minY = minTemp.y;
          }
          if (maxTemp.y > maxY) {
            maxY = maxTemp.y;
          }
        }
        return maxY - minY;
      }
    }
    

    Here is the sketch viewer in motion

    Widget sketchViewer(Sketch sketch){
        return SizedBox(
            height: sketch
                .getTotalHeight(),
            child: Scribble(
              notifier: ScribbleNotifier(
                  allowedPointersMode: ScribblePointerMode.mouseOnly,
                  sketch: sketch
                      .croppedSketch()),
              drawPen: true,
            ),
          );
    }
    

    I figured this could help somebody. I don't know if it is worth creating a pr for this or not.

    enhancement good first issue 
    opened by alexrabin 2
  • Change Previous Color on Background Color Change

    Change Previous Color on Background Color Change

    Hi,

    I've been trying to solve a problem but can't find the best way to do so. If I was drawing in black and then changed the background color to black, what would be the best way to change the previous lines that were black to white? The only thing I could think of was iterate through the JSON, find all of the instances that have the black color assignment and change it manually to a white color.

    Even this process, I am having trouble iterating through the JSON. Unfortunately, I am a newbie to Dart.

    opened by Nightbl927 2
  • How to load a json sketch?

    How to load a json sketch?

    I have learned that I can save a sketch with notifier.currentSketch.toJson(). I can recreate the sketch with Sketch.fromJson(json) but I cannot assign the new sketch to the state. Did I miss something?

    enhancement good first issue 
    opened by mikes222 2
  • How to get drawing offsets and position for digital handwriting recognizer?

    How to get drawing offsets and position for digital handwriting recognizer?

    Hello there,

    I'm going to make an app that can recognize text from digital ink written in scribble but I'm unable to figure out the local positions and offsets.

    opened by sujanbhattaraiofficial 1
  • add `allowedPointersMode` to `setEraser` and `setColor`

    add `allowedPointersMode` to `setEraser` and `setColor`

    Bug: When setEraser and setColor is called, allowedPointersMode will reset to default value ScribblePointerMode.all. Fix the situation like: allowedPointersMode is set to ScribblePointerMode.penOnly on ScribbleNotifier first, but mouse and touch will be allowed after change pen color or switch to eraser mode.

    opened by yujinlin0224 1
  • Render stroke in ScribblePainter using perfect_freehand

    Render stroke in ScribblePainter using perfect_freehand

    As per issue https://github.com/fyzio/scribble/issues/19, this integrates perfect_freehand to render strokes, pretty much just lifting the CustomerPainter recipe straight from the perfect_freehand docs. Example difference in rendering:

    Before

    current

    After

    with-perfect-freehand
    opened by mattrussell-sonocent 1
  • Straight line between two points?

    Straight line between two points?

    Is it possible to draw a straight line between two mouse pointer events? For example between onPointerDown and onPointerUp.

    I would like to be able to draw arrows or straight lines.

    opened by AlexMal 0
  • iOS Scribble (handwriting text input feature) conflicts with stylus stroke recognition

    iOS Scribble (handwriting text input feature) conflicts with stylus stroke recognition

    Thanks for the ace library!

    When using an Apple Pencil on iOS with Scribble input enabled, there's a delay of maybe 1 second before the library begins to recognise the stylus stroke. Before then, a temporary grey stroke is shown, which I suspect is the OS attempting handwriting recognition briefly. If I disable Settings -> Apple Pencil -> Scribble, then the library recognises the stroke immediately.

    iOS: 15.4.1 scribble: 0.4.0 Flutter: 2.10.5

    https://user-images.githubusercontent.com/45822724/169027476-cf7920e1-04f0-41b0-9fab-3fddcf8a0136.MP4

    opened by mattrussell-sonocent 3
  • Use 3D bezier curves instead of point lists and vertices

    Use 3D bezier curves instead of point lists and vertices

    Using 3D bezier curves instead of lines would allow for:

    • Simplification of lines (less space complexity)
    • Smoothing lines and pressure
    • "Perfect" collision detection for eraser
    • ... potentially more

    This is probably not gonna happen for a while (maybe for 1.0), since the current approach works well enough for our use-case, but we're always happy about PRs

    enhancement 
    opened by timcreatedit 2
  • It doesn't work on mobile browser

    It doesn't work on mobile browser

    Hello, thanks for the great library, the web version works very well on Desktop browser but unfortunately it has a lot of issues on mobile browser (I've tested it on iOS and Android, phones and tablets).

    Thanks, Dem

    bug 
    opened by demetrio812 3
Owner
Tim Created It.
Tim Created It.
A Flutter library to make Rest API clients more easily. Inspired by Java Feing.

A Flutter library to make Rest API clients more easily. Inspired by Java Feing. Features Facilitated JSON encode and decode using common interfaces. F

null 2 Mar 15, 2022
Minimal Dart wrapper to interact with Some Random Api. Easy to use, simplified and lightweight.

SRA - Some Random Api Minimal Dart wrapper to interact with Some Random Api. Easy to use, simplified and lightweight. Getting started Add the package

Yakiyo 3 Jan 4, 2023
null 9 Dec 1, 2022
A simple command-line application to generate simple folder and file structure for Flutter Applications

Kanza_cli is a simple command line tool to generate folder and file structure for your Flutter apps. To use it, you should do the followings: 1. First

Kanan Yusubov 9 Dec 16, 2022
A simple shortcut, command line interface (CLI) for a lazy (a.k.a effective) Flutter developer in order to increase productivity and happiness.

f A simple shortcut, command line interface (CLI) for a lazy (a.k.a effective) Flutter developer in order to increase productivity and happiness. Inst

Salman S 27 Nov 22, 2022
Uproot(uprt) is a multi-platform (Windows, MacOs, and Linux) command line utility written in Dart to convert a router's DHCP IP Reservations between routers

UPROOT Uproot(uprt) is a multi-platform (Windows, MacOs, and Linux) command line utility written in Dart to convert a router's DHCP IP Reservations be

GeekVisit 73 Jan 1, 2023
A tool to easily install the Android SDK command-line and platform tools.

gibadb A tool to easily install the Android SDK command-line and platform tools. For developers: This README describes the CLI tool that ships with th

null 3 Sep 22, 2022
A Very Good Command Line Interface for Dart created by Very Good Ventures 🦄

Very Good CLI Developed with ?? by Very Good Ventures ?? A Very Good Command Line Interface for Dart. Installing $ dart pub global activate very_good_

Very Good Open Source 1.8k Jan 8, 2023
A command-line interface for Ultroid

UltroidCli Welcome to the UltroidCli project built for The Ultroid. This Project is actively maintained by Akash Pattnaik. All kinds of contributions

AkashPattanaik 38 Nov 13, 2022
Command-line tool to provide null-safety percentage info of a project. Track your migration progress on mixed-version programs that execute with unsound null safety.

null_safety_percentage Command-line tool to provide null-safety percentage info of a project. Track your migration progress on mixed-version programs

dartside.dev 8 Mar 27, 2022
Easy to use cross-platform regex replace command line util

replace Easy to use cross-platform regex replace command line util. Can't remember the arguments to the find command? or how xargs works? Maybe sed is

Rob Becker 3 Feb 1, 2022
Command-line Interface (CLI) for any_icon_maker.

makeanyicon Command-line Interface (CLI) for any_icon_maker. makeanyicon Quick Start Installation Usage License Quick Start Installation dart pub glob

MakeAnyIcon 6 Nov 4, 2022
Arissounddart - a Command-line SoundSprite generator for Dart

SoundDart SoundDart is a Command-line SoundSprite generator for Dart. It require

Behruz Hurramov 1 Jan 9, 2022
Collects screen sizes and pixel densities for real iPhones, iPads, Google phones, Samsung phones, and more.

Device Sizes This package aggregates screen sizes and pixel densities for as many physical devices as possible. The purpose of this package is to help

Matt Carroll 16 Jan 8, 2023
Functional programming essentials for Dart. Tail call optimization, partial application, memoization, and more.

Pure is a Dart package that brings functional programming essentials through extension methods. It offers function composition/pipes, function memoization, partial application, and recursion trampolines.

Yakov Karpov 10 Oct 27, 2022
Dependency Injection is a great design pattern that allows us to eliminate rigid dependencies between elements and it makes the application more flexible

GetX lib DI pattern Dependency Injection is a great design pattern that allows us to eliminate rigid dependencies between elements and it makes the ap

Trương Việt Hoàng 4 Feb 1, 2022
Quiver is a set of utility libraries for Dart that makes using many Dart libraries easier and more convenient, or adds additional functionality.

Quiver is a set of utility libraries for Dart that makes using many Dart libraries easier and more convenient, or adds additional functionality.

Google 905 Jan 2, 2023
Official Git of flutter code-push made by Chimera inc. If you want to get more info or seek for biz corporation, you can contact [email protected].

中文版 Chimera Flutter Code Push Chimera is a Dart compiler developed by ourselves, which generates interpretable and executable bytecode to implement co

Waytoon 21 Oct 6, 2022