draw_your_image 0.4.2
draw_your_image: ^0.4.2 copied to clipboard
A Flutter package which enables users to draw with fingers in your designed UI.
draw_your_image #
draw_your_image is a Flutter package for drawing pictures/letters with fingers with Draw widget.
Draw is just a tiny widget which just feedbacks generated strokes by user's gestures and draws the given strokes on their screen.
Although this means all the state management for stroke data, such as undo/redo, clear, or preserving stroke values, is your business, this approach enables you to seemlessly collaborate with the functionalities of your apps, such as persistence of the strokes data, integrated undo/redo with other editings, etc.
Also, all the configuration about colors and stroke width, smoothing logics, strokes data can be customized.
Demos #

Features #
- Fully declarative
- Smoothing strokes
- All the data can be customized on your side
- Configuration of any stroke colors and background color
- Configuration of stroke width
- Erasor mode
Note #
Though this package is available with a couple of features, it's still under development. Any feedbacks or bug reports, and off course Pull Requests, are welcome. Feel free to visit the GitHub repository below.
https://github.com/chooyan-eng/draw_your_image
Usage #
Draw #
The very first step for draw_your_image is to place Draw widget at anywhere you want in the widget tree.
The Draw requires a parent widget, typically StatefulWidget, that manages the stroke state as Draw only handles the drawing interaction and draws the given strokes.
class MyDrawingPage extends StatefulWidget {
@override
_MyDrawingPageState createState() => _MyDrawingPageState();
}
class _MyDrawingPageState extends State<MyDrawingPage> {
List<Stroke> _strokes = [];
@override
Widget build(BuildContext context) {
return Draw(
// give back stroke data to Draw
strokes: _strokes,
onStrokeDrawn: (stroke) {
// Add a drawn stroke to _strokes
setState(() {
_strokes = [..._strokes, stroke];
});
},
backgroundColor: Colors.blue.shade50,
strokeColor: Colors.red,
strokeWidth: 8,
isErasing: false,
);
}
}
Required properties:
strokes: List ofStrokeobjects to display on the canvasonStrokeDrawn: Callback invoked when a stroke is completed
**Optional properties: **
strokeColor,strokeWidth:Drawwidget displays a canvas where users can draw with the given configurations.isErasing: A flag for erasing drawn strokes. Iftrue, new strokes will erase previously drawn strokes. Note that erasing strokes are also represented byStrokeand passed viaonStrokeDrawncallback as well.
If you wish to change colors or width, simply call setState() in your StatefulWidget and change the properties passed to Draw. Other state management systems are also available.
Input Device Control #
The onStrokeStarted callback allows you to control drawing behavior based on input situations, typically input device type (stylus, finger, mouse, etc.). This callback is invoked when a new stroke is about to start, giving you the opportunity to:
- Accept or reject the stroke based on the situation (e.g. accept only stylus)
- Modify stroke properties (color, width, erasing mode) based on input device
- Handle multi-touch scenarios by choosing which stroke to continue
Callback signature:
Stroke? Function(Stroke newStroke, Stroke? currentStroke)? onStrokeStarted
Parameters:
newStroke: The new stroke being started with initial configurationcurrentStroke: The currently ongoing stroke (if any)
Return value:
Draw continues the returned Stroke, thus:
- return
newStroketo start the new stroke - return
currentStroketo continue the existing stroke (rejecting the new one) - return
nullto cancel both strokes - return a modified stroke with
newStroke.copyWith(...)to customize properties
You can use the pre-defined utility functions, stylusOnlyHandler or stylusPriorHandler, if they fit your needs. Or you can also make your own function.
Example: Accept only stylus input #
import 'package:draw_your_image/draw_your_image.dart';
Draw(
strokes: _strokes,
onStrokeDrawn: (stroke) => setState(() => _strokes.add(stroke)),
onStrokeStarted: stylusOnlyHandler,
)
The stylusOnlyHandler utility function only accepts stylus input and automatically sets inverted stylus to erasing mode.
Example: Prioritize stylus over other inputs #
Draw(
strokes: _strokes,
onStrokeDrawn: (stroke) => setState(() => _strokes.add(stroke)),
onStrokeStarted: stylusPriorHandler,
)
The stylusPriorHandler utility function gives priority to stylus input. If a stylus is already drawing, other input types are ignored.
Example: Custom handler - Stylus draws black, finger erases #
Stroke? customHandler(Stroke newStroke, Stroke? currentStroke) {
// If stylus is already drawing, continue with it
if (currentStroke?.deviceKind == PointerDeviceKind.stylus) {
return currentStroke;
}
// New stroke: stylus draws black, finger erases
if (newStroke.deviceKind == PointerDeviceKind.stylus) {
return newStroke.copyWith(color: Colors.black, isErasing: false);
} else if (newStroke.deviceKind == PointerDeviceKind.touch) {
return newStroke.copyWith(isErasing: true);
}
// Reject other input types
return currentStroke;
}
Draw(
strokes: _strokes,
onStrokeDrawn: (stroke) => setState(() => _strokes.add(stroke)),
onStrokeStarted: customHandler,
)
Path Smoothing #
By default, strokes are smoothed using Catmull-Rom spline interpolation. You can customize this behavior using the smoothingFunc parameter.
Using built-in smoothing modes: #
You can use SmoothingMode enum for pre-defined smoothing logics. As SmoothingMode has a field of function named converter, you can simply pass the function to smoothingFunc.
// Smooth curves (default)
Draw(
strokes: _strokes,
onStrokeDrawn: (stroke) => setState(() => _strokes.add(stroke)),
smoothingFunc: SmoothingMode.catmullRom.converter,
)
// No smoothing (straight lines)
Draw(
strokes: _strokes,
onStrokeDrawn: (stroke) => setState(() => _strokes.add(stroke)),
smoothingFunc: SmoothingMode.none.converter,
)
Using custom smoothing function: #
Because smoothingFunc can receive any functions as long as they apply the type Path Function(Stroke), you can implement your own logic and pass the function here for customizing smoothing logics.
Draw(
strokes: _strokes,
onStrokeDrawn: (stroke) => setState(() => _strokes.add(stroke)),
smoothingFunc: (stroke) {
// Custom path generation logic
final path = Path();
final points = stroke.points;
if (points.isNotEmpty) {
path.moveTo(points[0].dx, points[0].dy);
for (int i = 1; i < points.length; i++) {
path.lineTo(points[i].dx, points[i].dy);
}
}
return path;
},
)
Stroke Data Structure #
Strokes are stored as a list of point and its metadata containing:
points: List ofOffsetrepresenting the stroke pathcolor: Stroke colorwidth: Stroke widthisErasing: Whether the stroke is in eraser mode
This structure allows you to:
- Access and process stroke data independently from UI
- Apply post-processing like resampling
- Implement custom rendering logic
Resampling Utility #
The resamplePoints function allows you to resample stroke points for uniform density:
import 'package:draw_your_image/draw_your_image.dart';
// Resample points with 5.0 pixel spacing
final originalPoints = [
Offset(0, 0),
Offset(10, 10),
Offset(20, 15),
// ... more points
];
final resampledPoints = resamplePoints(originalPoints, 5.0);
// Use resampled points to create a new stroke
final newStroke = stroke.copyWith(points: resampledPoints);
This is useful for:
- Reducing the number of points while maintaining shape
- Normalizing point density across different drawing speeds
- Pre-processing data for machine learning or gesture recognition