Search packages, repositories and app categories

GitHub repositoryfluttercandies/flutter_drawing_board

flutter_drawing_board

A new Flutter package of drawing board

flutter_drawing_board project cover
Stars
239
Forks
98
Last push (UTC)
Jan 20, 2026
Project status
Active

Original README

English project snapshot. Visit GitHub for the latest content.

Flutter Drawing Board

A powerful and highly customizable Flutter package for creating interactive drawing boards with advanced features.

English | 中文

pub package GitHub stars GitHub forks CodeFactor FlutterCandies

Breaking Changes: After version 0.3.0, text functionality has been removed. For text features, please use pub package

Version 0.9.9+ Behavior Changes:

  • SimpleLine() now uses Bezier curve smoothing by default (useBezierCurve: true), providing smoother lines. To use the old behavior, set useBezierCurve: false.
  • JSON import maintains backward compatibility with useBezierCurve: false as default.

Features

  • Rich Drawing Tools - SimpleLine, SmoothLine (brush), StraightLine, Rectangle, Circle, and Eraser
  • Advanced Smoothing - Bezier curves and Catmull-Rom spline interpolation for ultra-smooth lines
  • Palm Rejection - Prevent accidental palm touches on tablets
  • Canvas Operations - Pan, zoom, rotate (90° increments), undo, redo, clear
  • Performance Optimized - Point filtering, canvas caching (~70% improvement), optimized eraser (~50% improvement)
  • JSON Serialization - Complete save/load support for drawings
  • Image Export - Export to PNG and other formats
  • Flexible Toolbar System - Built-in toolbars with easy customization
  • Highly Extensible - Create custom drawing content types easily
  • Configurable History - Limit undo/redo steps to prevent memory growth (default: 100)

Preview

Try it online: Demo

Quick Start

import 'package:flutter_drawing_board/flutter_drawing_board.dart';

class MyDrawingPage extends StatefulWidget {
  @override
  State<MyDrawingPage> createState() => _MyDrawingPageState();
}

class _MyDrawingPageState extends State<MyDrawingPage> {
  final DrawingController _drawingController = DrawingController();

  @override
  void dispose() {
    _drawingController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          // Drawing Board
          Expanded(
            child: DrawingBoard(
              controller: _drawingController,
              background: Container(color: Colors.white),
            ),
          ),

          // Action Bar (slider, undo, redo, rotate, clear)
          DrawingBar(
            controller: _drawingController,
            tools: [
              DefaultActionItem.slider(),
              DefaultActionItem.undo(),
              DefaultActionItem.redo(),
              DefaultActionItem.turn(),
              DefaultActionItem.clear(),
            ],
          ),

          // Tool Bar (pen, brush, shapes, eraser)
          DrawingBar(
            controller: _drawingController,
            tools: [
              DefaultToolItem.pen(),
              DefaultToolItem.brush(),
              DefaultToolItem.rectangle(),
              DefaultToolItem.circle(),
              DefaultToolItem.straightLine(),
              DefaultToolItem.eraser(),
            ],
          ),
        ],
      ),
    );
  }
}

Usage Guide

1. Drawing Content Types

The package provides several built-in drawing content types:

SimpleLine - Free-form line

Draws smooth free-form lines with optional Bezier curve smoothing.

_drawingController.setPaintContent = SimpleLine(
  useBezierCurve: true,  // Enable Bezier smoothing (default: true since v0.9.9)
  minPointDistance: 2.0, // Filter points closer than this distance (default: 2.0)
);

Note: Since v0.9.9, useBezierCurve defaults to true for smoother lines. Set to false for the old straight-line behavior.

SmoothLine - Brush stroke with variable width

Draws brush strokes with pressure-like width variation and advanced smoothing.

_drawingController.setPaintContent = SmoothLine(
  brushPrecision: 0.8,   // Line smoothness factor (smaller = smoother, default: 0.8)
  useBezierCurve: true,  // Enable Bezier curves (default: true)
  minPointDistance: 2.0, // Filter redundant points (default: 2.0)
  smoothLevel: 1,        // 0: fast, 1: balanced, 2: ultra-smooth (default: 1)
);

Smooth Levels:

  • 0: Fast - No additional smoothing
  • 1: Balanced - Basic smoothing (recommended)
  • 2: Ultra-smooth - Catmull-Rom spline interpolation for silky smooth curves
StraightLine - Straight line
_drawingController.setPaintContent = StraightLine();
Rectangle - Rectangle shape
_drawingController.setPaintContent = Rectangle();
Circle - Circle or Ellipse
_drawingController.setPaintContent = Circle(
  isEllipse: false,       // false: circle, true: ellipse (default: false)
  startFromCenter: true,  // true: start from center, false: diagonal (default: true)
);
Eraser
_drawingController.setPaintContent = Eraser();
2. DrawingController API
Creating a Controller
final DrawingController _drawingController = DrawingController(
  config: DrawConfig(
    contentType: SimpleLine,
    strokeWidth: 4.0,
    color: Colors.black,
  ),
  maxHistorySteps: 100, // Limit undo/redo history (default: 100)
);
Setting Paint Style
_drawingController.setStyle(
  color: Colors.blue,
  strokeWidth: 6.0,
  strokeCap: StrokeCap.round,
  strokeJoin: StrokeJoin.round,
  blendMode: BlendMode.srcOver,
  isAntiAlias: true,
);
Canvas Operations
// Undo last action
_drawingController.undo();

// Redo last undone action
_drawingController.redo();

// Rotate canvas 90° clockwise
_drawingController.turn();

// Clear entire canvas
_drawingController.clear();

// Check operation availability
bool canUndo = _drawingController.canUndo();
bool canRedo = _drawingController.canRedo();
bool canClear = _drawingController.canClear();
Adding Content Programmatically
// Add single content
_drawingController.addContent(StraightLine.fromJson(jsonData));

// Add multiple contents
_drawingController.addContents([
  SimpleLine.fromJson(line1Json),
  Rectangle.fromJson(rect1Json),
]);
Exporting Data

Export as Image:

// Get complete image (with background)
Future<void> _exportImage() async {
  final ByteData? data = await _drawingController.getImageData(
    format: ui.ImageByteFormat.png,
  );

  if (data != null) {
    final Uint8List bytes = data.buffer.asUint8List();
    // Save or display the image
  }
}

// Get surface image (drawing only, faster)
Future<void> _exportSurface() async {
  final ByteData? data = await _drawingController.getSurfaceImageData();
  // Use the image data
}

Export as JSON:

void _exportJson() {
  final List<Map<String, dynamic>> jsonList = _drawingController.getJsonList();
  final String jsonString = const JsonEncoder.withIndent('  ').convert(jsonList);
  // Save or share the JSON
}

Import from JSON:

void _importJson(List<Map<String, dynamic>> jsonData) {
  final contents = jsonData.map((json) {
    final type = json['type'] as String;
    switch (type) {
      case 'SimpleLine':
        return SimpleLine.fromJson(json);
      case 'StraightLine':
        return StraightLine.fromJson(json);
      case 'Rectangle':
        return Rectangle.fromJson(json);
      case 'Circle':
        return Circle.fromJson(json);
      case 'Eraser':
        return Eraser.fromJson(json);
      default:
        return null;
    }
  }).whereType<PaintContent>().toList();

  _drawingController.addContents(contents);
}
3. DrawingBoard Configuration
DrawingBoard(
  controller: _drawingController,
  background: Container(color: Colors.white),

  // Interaction callbacks
  onPointerDown: (PointerDownEvent event) { /* ... */ },
  onPointerMove: (PointerMoveEvent event) { /* ... */ },
  onPointerUp: (PointerUpEvent event) { /* ... */ },

  // Canvas interaction
  boardPanEnabled: true,        // Enable panning (default: true)
  boardScaleEnabled: true,      // Enable zooming (default: true)
  minScale: 0.2,                // Minimum zoom scale (default: 0.2)
  maxScale: 20,                 // Maximum zoom scale (default: 20)
  panAxis: PanAxis.free,        // Pan direction constraint (default: free)

  // Advanced features
  enablePalmRejection: false,   // Enable palm rejection for tablets (default: false)

  // Layout
  alignment: Alignment.topCenter,
  clipBehavior: Clip.antiAlias,
  boardClipBehavior: Clip.hardEdge,

  // Transformation controller for external manipulation
  transformationController: _transformationController,
)
4. Toolbar System
DrawingBar

The DrawingBar widget provides a flexible toolbar layout with automatic controller passing.

// Horizontal toolbar (default)
DrawingBar(
  controller: _drawingController,
  style: HorizontalToolsBarStyle(
    mainAxisAlignment: MainAxisAlignment.center,
    spacing: 8.0,
  ),
  tools: [ /* tool widgets */ ],
)

// Vertical toolbar
DrawingBar(
  controller: _drawingController,
  style: VerticalToolsBarStyle(
    mainAxisAlignment: MainAxisAlignment.start,
    spacing: 8.0,
  ),
  tools: [ /* tool widgets */ ],
)

// Wrap toolbar (auto-wrapping)
DrawingBar(
  controller: _drawingController,
  style: WrapToolsBarStyle(
    spacing: 8.0,
    runSpacing: 8.0,
  ),
  tools: [ /* tool widgets */ ],
)
Default Tool Items

Pre-built tool buttons for switching drawing content:

DefaultToolItem.pen()          // SimpleLine tool
DefaultToolItem.brush()        // SmoothLine tool
DefaultToolItem.straightLine() // StraightLine tool
DefaultToolItem.rectangle()    // Rectangle tool
DefaultToolItem.circle()       // Circle tool
DefaultToolItem.eraser()       // Eraser tool

Custom tool item:

DefaultToolItem(
  icon: Icons.star,
  content: MyCustomContent,
  onTap: (controller) {
    // Custom action
  },
  color: Colors.grey,
  activeColor: Colors.blue,
  iconSize: 24,
)
Default Action Items

Pre-built action buttons:

DefaultActionItem.slider()     // Stroke width slider
DefaultActionItem.undo()       // Undo button
DefaultActionItem.redo()       // Redo button
DefaultActionItem.turn()       // Rotate 90° button
DefaultActionItem.clear()      // Clear canvas button

Custom action item:

DefaultActionItem(
  childBuilder: (context, controller) {
    return Icon(Icons.save);
  },
  onTap: (controller) async {
    // Save drawing
    await _saveDrawing(controller);
  },
)
5. Advanced Features
Palm Rejection

Prevents accidental palm touches on tablets by detecting large touch areas and rapid successive touches.

DrawingBoard(
  controller: _drawingController,
  background: Container(color: Colors.white),
  enablePalmRejection: true, // Enable palm rejection
)

How it works:

  • Rejects touches with size > 15.0 (likely palm)
  • Rejects rapid successive touches within 100ms (palm + finger)
History Management

Control memory usage by limiting undo/redo history:

final DrawingController _drawingController = DrawingController(
  maxHistorySteps: 50, // Limit to 50 steps (default: 100)
);
Custom Colors with Opacity
// Set color with custom opacity
_drawingController.setStyle(
  color: Colors.red.withValues(alpha: 0.5),
);
6. Creating Custom Drawing Content

Extend PaintContent to create your own drawing tools:

class Triangle extends PaintContent {
  Triangle();

  Triangle.data({
    required this.startPoint,
    required this.A,
    required this.B,
    required this.C,
    required Paint paint,
  }) : super.paint(paint);

  factory Triangle.fromJson(Map<String, dynamic> data) {
    return Triangle.data(
      startPoint: jsonToOffset(data['startPoint'] as Map<String, dynamic>),
      A: jsonToOffset(data['A'] as Map<String, dynamic>),
      B: jsonToOffset(data['B'] as Map<String, dynamic>),
      C: jsonToOffset(data['C'] as Map<String, dynamic>),
      paint: jsonToPaint(data['paint'] as Map<String, dynamic>),
    );
  }

  Offset startPoint = Offset.zero;
  Offset A = Offset.zero;
  Offset B = Offset.zero;
  Offset C = Offset.zero;

  @override
  void startDraw(Offset startPoint) => this.startPoint = startPoint;

  @override
  void drawing(Offset nowPoint) {
    A = Offset(
      startPoint.dx + (nowPoint.dx - startPoint.dx) / 2,
      startPoint.dy,
    );
    B = Offset(startPoint.dx, nowPoint.dy);
    C = nowPoint;
  }

  @override
  void draw(Canvas canvas, Size size, bool deeper) {
    final Path path = Path()
      ..moveTo(A.dx, A.dy)
      ..lineTo(B.dx, B.dy)
      ..lineTo(C.dx, C.dy)
      ..close();

    canvas.drawPath(path, paint);
  }

  @override
  Triangle copy() => Triangle();

  @override
  Map<String, dynamic> toContentJson() {
    return <String, dynamic>{
      'startPoint': startPoint.toJson(),
      'A': A.toJson(),
      'B': B.toJson(),
      'C': C.toJson(),
      'paint': paint.toJson(),
    };
  }
}

Add to toolbar:

DrawingBar(
  controller: _drawingController,
  tools: [
    ...DefaultToolItem.values, // All default tools
    DefaultToolItem(
      icon: Icons.change_history,
      content: Triangle,
      activeColor: Colors.purple,
    ),
  ],
)

Preview:

Performance Optimizations

The package includes several performance optimizations (introduced in v0.9.9+):

  • Canvas Cache (~70% improvement) - Avoids redundant image generation through cache version control
  • Eraser Optimization (~50% improvement) - Reduces double refresh during eraser operations
  • Point Filtering (30-50% reduction) - Filters redundant points via minPointDistance, reducing data by 30-50%
  • Bezier Smoothing - Eliminates jagged lines without sacrificing performance
  • Overall - 30-50% improvement in rendering and memory usage

JSON Format Example

[
  {
    "type": "StraightLine",
    "startPoint": {"dx": 114.56, "dy": 117.50},
    "endPoint": {"dx": 252.93, "dy": 254.91},
    "paint": {
      "blendMode": 3,
      "color": 4294198070,
      "filterQuality": 3,
      "invertColors": false,
      "isAntiAlias": false,
      "strokeCap": 1,
      "strokeJoin": 1,
      "strokeWidth": 4.0,
      "style": 1
    }
  },
  {
    "type": "SimpleLine",
    "points": [
      {"dx": 100.0, "dy": 100.0},
      {"dx": 150.0, "dy": 120.0},
      {"dx": 200.0, "dy": 110.0}
    ],
    "useBezierCurve": true,
    "minPointDistance": 2.0,
    "paint": { /* ... */ }
  }
]

API Reference

DrawingController
Method Description
setStyle({...}) Update paint style (color, width, etc.)
setPaintContent(PaintContent) Switch drawing tool
undo() Undo last action
redo() Redo last undone action
turn() Rotate canvas 90°
clear() Clear canvas
addContent(PaintContent) Add single content
addContents(List<PaintContent>) Add multiple contents
getImageData() Export as image (with background)
getSurfaceImageData() Export surface image (faster)
getJsonList() Export as JSON
Built-in Content Types
Type Description Parameters
SimpleLine Free-form line useBezierCurve, minPointDistance
SmoothLine Brush stroke brushPrecision, smoothLevel, useBezierCurve, minPointDistance
StraightLine Straight line -
Rectangle Rectangle -
Circle Circle/Ellipse isEllipse, startFromCenter
Eraser Eraser -

Examples

Check out the example folder for complete examples including:

  • Basic drawing board
  • Custom drawing content (Triangle, Image)
  • Color picker integration
  • JSON import/export
  • Image export

Contributing

Contributions are welcome! Here's how you can help:

  1. Report bugs - Open an issue describing the bug and how to reproduce it
  2. Suggest features - Open an issue describing your feature request
  3. Submit PRs - Fork the repo, make your changes, and submit a pull request

Please ensure your code follows the existing style and includes tests where applicable.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Community

Join our QQ group: FlutterCandies

Changelog

See CHANGELOG.md for version history.

FlutterCandies GitHub avatar
GitHub author

FlutterCandies ↗

Custom Flutter candies (packages) for you to build your Flutter app easily. Enjoy it!

LanguagesDartC++CMakeSwiftCHTMLKotlinObjective-C

Packages published by this repository

Dependencies used

Dependency list 2 items

  • flutter{"sdk":"flutter"}
  • flutter_testDevelopment{"sdk":"flutter"}

How do I choose a repository?

Start with your use case, compare repository activity, and check the original project before adopting it.

  1. Search by repository name, owner or description, then narrow the results by topic.
  2. Compare stars, forks and recent pushes. Stars show community interest, not a guarantee of quality.
  3. Read the README, license and issues on GitHub before using a project.

Sources and maintenance

“A repository is the most basic element of GitHub.”

GitHub Docs — About repositories ↗