Search code examples
fluttercanvasdartdraggableshapes

Flutter shape resizing and dragging over image back-grounded canvas


I want to create different kind of shapes (like a rectangle, line or circle) over an image background canvas and edit those shape with corner points for resizing. How can I able to do that with flutter?

Shapes customization dragging corners is similar to this JavaScript framework Fabric.js

and the result should be like this:

Should be like this


Solution

  • I solved the issue by writing this below code:

    The first part of the code from Scaffold:

    if (_imageWidget != null) ...[
                FittedBox(
                  fit: BoxFit.fill,
                  child: GestureDetector(
                    onPanStart: (DragStartDetails details) {
                      // get distance from points to check if is in circle
                      int indexMatch = -1;
                      for (int i = 0; i < _points.length; i++) {
                        double distance = sqrt(
                            pow(details.localPosition.dx - _points[i].dx, 2) +
                                pow(details.localPosition.dy - _points[i].dy, 2));
                        if (distance <= 30) {
                          indexMatch = i;
                          break;
                        }
                      }
                      if (indexMatch != -1) {
                        _currentlyDraggedIndex = indexMatch;
                      }
                    },
                    onPanUpdate: (DragUpdateDetails details) {
                      if (_currentlyDraggedIndex != -1) {
                        setState(() {
                          _points = List.from(_points);
                          _points[_currentlyDraggedIndex] = details.localPosition;
                        });
                      }
                    },
                    onPanEnd: (_) {
                      setState(() {
                        _currentlyDraggedIndex = -1;
                      });
                    },
                    child: SizedBox(
                      width: _image.width.toDouble(),
                      height: _image.height.toDouble(),
                      child: CustomPaint(
                        painter: RectanglePainter(
                            points: _points, clear: _clear, image: _image),
                      ),
                    ),
                  ),
                )
              ]
    

    The Second Part

    class RectanglePainter extends CustomPainter {
      List<Offset> points;
      bool clear;
      final ui.Image image;
    
      RectanglePainter(
          {@required this.points, @required this.clear, @required this.image});
    
      @override
      void paint(Canvas canvas, Size size) {
        final paint = Paint()
          ..color = Colors.red
          ..strokeCap = StrokeCap.square
          ..style = PaintingStyle.fill
          ..strokeWidth = 2;
    
        final outputRect =
            Rect.fromPoints(ui.Offset.zero, ui.Offset(size.width, size.height));
        final Size imageSize =
            Size(image.width.toDouble(), image.height.toDouble());
        final FittedSizes sizes =
            applyBoxFit(BoxFit.contain, imageSize, outputRect.size);
        final Rect inputSubrect =
            Alignment.center.inscribe(sizes.source, Offset.zero & imageSize);
        final Rect outputSubrect =
            Alignment.center.inscribe(sizes.destination, outputRect);
        canvas.drawImageRect(image, inputSubrect, outputSubrect, paint);
        if (!clear) {
          final circlePaint = Paint()
            ..color = Colors.red
            ..strokeCap = StrokeCap.square
            ..style = PaintingStyle.fill
            ..blendMode = BlendMode.multiply
            ..strokeWidth = 2;
    
          for (int i = 0; i < points.length; i++) {
            if (i + 1 == points.length) {
              canvas.drawLine(points[i], points[0], paint);
            } else {
              canvas.drawLine(points[i], points[i + 1], paint);
            }
            canvas.drawCircle(points[i], 10, circlePaint);
          }
        }
      }
    
      @override
      bool shouldRepaint(RectanglePainter oldPainter) =>
          oldPainter.points != points || clear;
    }