flutter_sketchpad 0.0.1
flutter_sketchpad: ^0.0.1 copied to clipboard
Annotate images in Flutter: pressure-sensitive freehand drawing, text, shapes, layers, measurement tools, and sharp vector-rendered PNG export.
flutter_sketchpad #
Annotate images in Flutter — pressure-sensitive freehand drawing, text, shapes, layers, and measurement tools, with true vector export.
This is an annotation editor over an image, not a blank sketch pad. Reach for it when you need to mark up something: a diagram, a photo, a screenshot, a clinical body chart, an inspection report.
Features #
- Freehand strokes with real stylus pressure and variable width, so a line thickens and tapers as you press and release. Devices that report no pressure get a taper simulated from stroke velocity instead of a flat ribbon.
- Text and shapes (circle, box, triangle, diamond, star, arrow, double arrow, line, cross, check) that drag, pinch-scale and rotate, with π/4 rotation snapping, centre alignment guides, duplicate and delete handles.
- Layers with per-layer visibility and opacity — show one pass of annotations against another on the same background.
- Measurement tool with real-world calibration: set pixels-per-unit once and distances read out in cm, inches, or whatever you like.
- Highlighter and eraser. An eraser is confined to its own layer, so it cannot punch a hole through the background.
- Stylus-only input mode — palm rejection for tablet use.
- Serializable scenes. A sketch is plain JSON that reopens as live, editable strokes, not a flattened image.
- Vector export. Output is re-rendered from the vector content at whatever resolution you ask for, rather than screenshotting the widget, so it stays sharp when zoomed in.
- No assets, no Material dependency, no global side effects. The canvas never changes device orientation or system UI, and it sizes to its constraints — it does not have to be full-screen.
Install #
dependencies:
flutter_sketchpad: ^0.0.1
Quick start #
The package does not load images: hand it a dart:ui Image from wherever you
like, and tell the controller its size.
import 'package:flutter_sketchpad/flutter_sketchpad.dart';
final controller = SketchpadController()..tool = SketchTool.pen;
// Somewhere after you have decoded your background:
controller.setImageSize(Size(image.width.toDouble(), image.height.toDouble()));
Sketchpad(
controller: controller,
background: image,
)
Sketchpad draws the canvas and handles gestures. It ships no toolbar — what
tools to offer and how they should look is your app's decision. Drive the
controller from your own buttons:
controller.tool = SketchTool.highlighter;
controller.color = 0xFFEF4444; // ARGB int, not a Color
controller.strokeWidth = 8;
controller.addShape(ShapeKind.arrow, position: centreOfImage);
controller.undo();
controller.clear();
Text editing is likewise yours, so the package imposes no design language.
onOverlayDoubleTap fires when a selected annotation is tapped again:
Sketchpad(
controller: controller,
background: image,
onOverlayDoubleTap: (overlay) async {
if (overlay is! TextOverlay) return;
final text = await myTextDialog(initial: overlay.text);
if (text != null) controller.updateText(overlay, text);
},
)
Saving and reopening #
A SketchScene is the whole document. Store the JSON and the sketch stays
editable forever:
final json = controller.scene.toJsonString(); // save this
controller.loadScene(
SketchScene.fromJsonString(json),
backgroundSize: Size(image.width.toDouble(), image.height.toDouble()),
);
Always pass backgroundSize. Scene coordinates are absolute image pixels,
so a sketch authored against a 1024px-wide image would draw at quarter scale, in
the wrong place, if the same image were later served at 4096px.
backgroundSize rescales the scene onto the image you actually loaded. It is
the single most common way to get this wrong.
Store the flattened PNG too if you want cheap thumbnails — but keep the JSON, because that is the part you can still edit.
Export #
final png = await controller.exportPng(
background: image,
maxDimension: 2048, // longest edge; supersamples vectors up to 4x
);
Hidden layers are excluded from export, matching what is on screen.
Layers #
final id = controller.addLayer(name: 'Follow-up');
controller.setActiveLayer(id); // new marks go here
controller.setLayerVisible(id, false); // not undoable — it is a view
controller.setLayerOpacity(id, 0.4);
controller.removeLayer(id); // also removes its content
Visibility and opacity deliberately do not enter the undo history: changing how you look at a document is not an edit to it, and undoing back through view toggles is surprising.
Measurement #
controller.calibration = SketchCalibration(pixelsPerUnit: 30, unit: 'cm');
controller.tool = SketchTool.measure; // then drag on the canvas
Establish pixelsPerUnit from a known reference in the image: if a 10 cm scale
bar spans 300 pixels, it is 30. Without a calibration, measurements read in
pixels.
Coordinate space #
Everything in a scene — stroke points, overlay positions, stroke widths — is in
image pixels, not screen pixels. That is what makes a scene independent of
zoom, device size and orientation, and it is why the same scene renders
identically on screen, in a SketchView, and in an export.
Colours are stored as 32-bit ARGB ints rather than Color objects, which
keeps the wire format independent of Flutter's colour API — that API has changed
representation more than once.
Read-only display #
SketchView(scene: savedScene, background: image)
Same renderer as the editor, no gesture handling.
Example #
cd example && flutter run
The example generates its background image at runtime — no bundled assets — and exercises every feature, including a 10 cm scale bar you can calibrate the measurement tool against and check by eye.
Status #
Pre-release (0.0.1), not yet published. Working and tested:
- scene model and JSON format (v1), layers, calibration
- renderer, PNG export, read-only view
- interactive canvas: drawing, highlighter, eraser, measurement, overlay transforms, pan/zoom, undo/redo, stylus-only mode
Not implemented yet:
- PDF and SVG export. Planned.
dart:ui.Canvasand thepdfpackage use different drawing APIs, so this needs a small backend abstraction over the geometry rather than a drop-in. - Stylus hover preview — showing the brush before the pen touches down.
- Per-layer picture caching. Committed strokes are currently re-rendered every frame; drawing over a very complex scene will cost more than it should.
- A built-in default toolbar. For now, see the example's toolbar.
License #
MIT