13

I have something like this:

CustomPaint(
       painter: CurvePainter(),
)

In this class I am doing my painting:

import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import './myState.dart';
import './models/mode.dart';
final String rawSvg = '''<svg viewBox="...">...</svg>''';

class CurvePainter extends CustomPainter {
  MyState _myState;
  DrawableRoot svgRoot;
  CurvePainter(MyState myState) {
    this._myState = myState;
    this.loadAsset();
  }

  void loadAsset() async {
    this.svgRoot = await svg.fromSvgString(rawSvg, rawSvg);// The canvas that is your board.
  }

  @override
  void paint (Canvas canvas, Size size) {
    canvas.translate(_myState.translateX, _myState.translateY);
    if(this.svgRoot != null){
        svgRoot.scaleCanvasToViewBox(canvas, size);
        svgRoot.clipCanvasToViewBox(canvas);
        // svgRoot.draw(canvas, size);
    }
}

Somebody know how to draw a SVG inside paint method? I found this library https://pub.dev/packages/flutter_svg#-readme-tab- . With my code I get error: Unhandled Exception: Bad state: viewBox element must be 4 elements long

I would be nice, if I can scale and rotate the svg inside canvas. But this is optional.

otto
  • 1,815
  • 7
  • 37
  • 63

3 Answers3

15

From the README:

import 'package:flutter_svg/flutter_svg.dart';
final String rawSvg = '''<svg viewBox="...">...</svg>''';
final DrawableRoot svgRoot = await svg.fromSvgString(rawSvg, rawSvg);

// If you only want the final Picture output, just use
final Picture picture = svgRoot.toPicture();

// Otherwise, if you want to draw it to a canvas:
// Optional, but probably normally desirable: scale the canvas dimensions to
// the SVG's viewbox
svgRoot.scaleCanvasToViewBox(canvas);

// Optional, but probably normally desireable: ensure the SVG isn't rendered
// outside of the viewbox bounds
svgRoot.clipCanvasToViewBox(canvas);
svgRoot.draw(canvas, size);

Which you could adapt as:

class CurvePainter extends CustomPainter {
  CurvePainter(this.svg);

  final DrawableRoot svg;
  @override
  void paint(Canvas canvas, Size size) {
       canvas.drawLine(...);
       svg.scaleCanvasToViewBox(canvas);
       svg.clipCanvasToViewBox(canvas);
       svg.draw(canvas, size);
  }
}

I'd advise finding some way to get the asynchronous part earlier on in your app, perhaps using a FutureBuilder or a ValueListenableBuilder.

Disclosure: I'm the author/primary maintainer of Flutter SVG.

Dan Field
  • 20,885
  • 5
  • 55
  • 71
  • thx. I get this error: Unhandled Exception: Bad state: viewBox element must be 4 elements long – otto Sep 10 '19 at 16:38
  • Your viewBox has to be in the format of `"1 2 3 4"`, with four numbers in it. – Dan Field Sep 10 '19 at 17:38
  • Hey @DanField I am working with the custom slider where I want to use the SVG icon in customPainter i am a little bit confused can you check the question.i have Added the link below: https://stackoverflow.com/questions/62001542/adding-the-svg-icon-in-the-custom-slider-using-the-custom-painter – Sagar Acharya May 25 '20 at 16:42
  • It doesn't work like this on version `0.22.0`. Method parameters don't match anymore. Now, `scaleCanvasToViewBox` requires `size`, and `draw` requires a `Rect`. – Roc Boronat Aug 31 '21 at 04:12
  • In case someone needs it, here's how you would write it on version `0.22.0`. Please Dan, correct me if I'm wrong :·) ```dart svg.scaleCanvasToViewBox(canvas, size); svg.clipCanvasToViewBox(canvas); svg.draw(canvas, Rect.fromLTWH(0, 0, size.width, size.height)); ``` – Roc Boronat Aug 31 '21 at 04:26
  • @DanField How do you set the svg's color? – SePröbläm Jun 29 '22 at 20:22
  • @DanField Can you update the answer to match the newest flutter_svg 2.0.7 version? – Fran Aug 31 '23 at 12:34
6

Ultimately I found drawing SVGs directly in Canvas to be cumbersome. Instead, I copied the SVG paths and transforms to Dart code using path_drawing and rendered them as Paths with Canvas.drawPath. This has the advantage of not even being an asset at all; the SVG data is literally code at this point. And you can convert back to an SVG easily. The process goes a bit like this:

  1. Add path_drawing: 0.4.1 to pubspec.yaml, flutter pub get, in the file you're rendering from import 'package:path_drawing/path_drawing.dart';.
  2. Copy-paste all paths from your SVG with the method parseSvgPathData as Path constants. (Path strings look something like M 86.102000,447.45700 L 86.102000,442.75300 .....)
    • You can combine many paths if there are more than one in the SVG:
      • static final Path complexPathToDraw = parseSvgPathData("path_1").addPath(parseSvgPathData("path_2"));.
  3. Usually the SVG will be wrapped in some translations (<g transform='translate()>). And drawPath can only render the Path from the top-left position. So you have to translate to the appropriate position. When rendering, translate the canvas before drawing (1) first to correct for the translations in the SVG, (2) next to scale to the size you want, (3) to go to the position you really want it on the Canvas. Then draw, and restore the Canvas to its untransformed state. But keep in mind, these matrices are added in reverse order to how we logically break it down because linear algebra is stupid.
    • canvas.save();
      canvas.translate(dxToRenderPosition, dyToRenderPosition);
      canvas.scale(sxFromSvgSizeToDesiredRenderSize);
      canvas.translate(dxFromSvg, dyFromSvg);
      canvas.drawPath(complexPathToDraw, Paint());
      canvas.restore();
      
    
    
WarDoctor113
  • 141
  • 2
  • 5
  • 1
    Just for the records: [Flutter shape maker](https://fluttershapemaker.com/) exports copy&paste ready `Path`s, sparing you from the additional dependency. – SePröbläm Jun 29 '22 at 21:32
1

I faced a problem similar to this where I wanted to draw a Svg scaled down on a small part of a canvas.

To make it work, I had to use this code:

Size desiredSize = Size(60, 40);
// get the svg from a preloaded array of DrawableRoot corresponding to all the Svg I might use
final DrawableRoot svgRoot = drawables[i];
canvas.save();
// [center] below is the Offset of the center of the area where I want the Svg to be drawn
canvas.translate(center.dx - desiredSize.width / 2, center.dy - desiredSize.height / 2);
Size svgSize = svgRoot.viewport.size;
var matrix = Matrix4.identity();
matrix.scale(desiredSize.width / svgSize.width, desiredSize.height / svgSize.height);
canvas.transform(matrix.storage);
svgRoot.draw(canvas, Rect.zero); // the second argument is not used in DrawableRoot.draw() method
canvas.restore();

This way, you can have complex Svg rendered on the canvas and still do some work on it.

E.g.: you can draw multiple Svg on the same canvas and draw over them.

leb1755
  • 1,386
  • 2
  • 14
  • 29