You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter技术问询:如何在图片上绘图并获取新图?如何绘制自由路径?

Flutter: Freehand Drawing on Images & Saving the Result

Hey there! I’ve got you covered on both your requirements—let’s walk through how to implement freehand drawing (like those red lines you mentioned) on an image and then save the final edited image.


1. Implementing Freehand Drawing on an Image

To draw free paths over an image, we’ll use GestureDetector to capture touch inputs and CustomPainter to render both the image and your drawn paths. Here’s a complete, ready-to-use example:

Step 1: Create a Stateful Widget to Track Drawing Paths

We’ll maintain the list of touch points and the target image in the widget state to update the drawing in real time:

import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'dart:typed_data';

class ImageDrawingWidget extends StatefulWidget {
  final ui.Image image;

  const ImageDrawingWidget({super.key, required this.image});

  @override
  State<ImageDrawingWidget> createState() => _ImageDrawingWidgetState();
}

class _ImageDrawingWidgetState extends State<ImageDrawingWidget> {
  final List<Offset> _drawPoints = [];
  final GlobalKey _repaintKey = GlobalKey(); // For capturing the final image

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
          child: GestureDetector(
            // Capture initial touch down
            onPanDown: (details) {
              setState(() {
                final RenderBox renderBox = context.findRenderObject() as RenderBox;
                _drawPoints.add(renderBox.globalToLocal(details.globalPosition));
              });
            },
            // Capture touch movement to draw continuous lines
            onPanUpdate: (details) {
              setState(() {
                final RenderBox renderBox = context.findRenderObject() as RenderBox;
                _drawPoints.add(renderBox.globalToLocal(details.globalPosition));
              });
            },
            // Add a marker to end the current path (optional, for multi-segment drawing)
            onPanEnd: (details) => setState(() => _drawPoints.add(Offset.infinite)),
            child: RepaintBoundary(
              key: _repaintKey,
              child: CustomPaint(
                size: Size(widget.image.width.toDouble(), widget.image.height.toDouble()),
                painter: _ImagePainter(image: widget.image, drawPoints: _drawPoints),
              ),
            ),
          ),
        ),
        ElevatedButton(
          onPressed: () async => await _saveEditedImage(),
          child: const Text("Save Drawn Image"),
        )
      ],
    );
  }
}

Step 2: Create the CustomPainter to Render Image and Paths

This painter will first draw the base image, then render your freehand path with the style you want (like thick red lines):

class _ImagePainter extends CustomPainter {
  final ui.Image image;
  final List<Offset> drawPoints;

  _ImagePainter({required this.image, required this.drawPoints});

  @override
  void paint(Canvas canvas, Size size) {
    // Draw the base image first
    canvas.drawImage(image, Offset.zero, Paint());

    // Draw the freehand path
    if (drawPoints.isNotEmpty) {
      final paint = Paint()
        ..color = Colors.red
        ..strokeWidth = 4.0
        ..strokeCap = StrokeCap.round // Makes line ends smooth
        ..style = PaintingStyle.stroke;

      for (int i = 0; i < drawPoints.length - 1; i++) {
        // Skip the infinite offset markers that split paths
        if (drawPoints[i] != Offset.infinite && drawPoints[i+1] != Offset.infinite) {
          canvas.drawLine(drawPoints[i], drawPoints[i+1], paint);
        }
      }
    }
  }

  @override
  bool shouldRepaint(covariant _ImagePainter oldDelegate) {
    // Only repaint if the drawing points or image change
    return oldDelegate.drawPoints != drawPoints || oldDelegate.image != image;
  }
}

2. Saving the Drawn Image as a New File/Image Object

To get the final edited image, we’ll use RepaintBoundary to capture the rendered widget, convert it to an image, and then process it. Add this method to your state class:

Future<void> _saveEditedImage() async {
  // Capture the rendered widget as an image
  final RenderRepaintBoundary boundary = _repaintKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
  final ui.Image editedImage = await boundary.toImage(pixelRatio: 3.0); // Higher ratio = sharper output

  // Convert the image to bytes (PNG format)
  final ByteData? byteData = await editedImage.toByteData(format: ui.ImageByteFormat.png);
  final Uint8List? imageBytes = byteData?.buffer.asUint8List();

  if (imageBytes != null) {
    // Here you can save the bytes to a file (using path_provider to get a local path)
    // Or pass the bytes to another widget, upload to a server, etc.
    print("Edited image captured successfully! Byte count: ${imageBytes.length}");
  }
}

Quick Tips to Customize

  • Adjust strokeWidth, color, and strokeCap in the Paint object to match your exact desired line style.
  • If loading the base image from assets or network, decode it to ui.Image first: use rootBundle.load() + ui.decodeImageFromList() for assets, or NetworkImage with resolve() for remote images.

内容的提问来源于stack exchange,提问作者said bendrioue

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:20:40