Flutter create Google maps custom marker from canvas

846

Use this class and call only the function createBitmapDescriptorFromIconData each time you want to create a bitmapIcon

class MarkerGenerator {
  final _markerSize;
  double _circleStrokeWidth;
  double _circleOffset;
  double _outlineCircleWidth;
  double _fillCircleWidth;
  double _iconSize;
  double _iconOffset;

  MarkerGenerator(this._markerSize) {
    // calculate marker dimensions
    _circleStrokeWidth = _markerSize / 12.0;
    _circleOffset = _markerSize / 2;
    _outlineCircleWidth = _circleOffset - (_circleStrokeWidth / 2);
    _fillCircleWidth = _markerSize / 2;
    final outlineCircleInnerWidth = _markerSize - (2 * _circleStrokeWidth);
    _iconSize = sqrt(pow(outlineCircleInnerWidth, 2) / 4);
    final rectDiagonal = sqrt(2 * pow(_markerSize, 2));
    final circleDistanceToCorners = (rectDiagonal - outlineCircleInnerWidth) / 2;
    _iconOffset = sqrt(pow(circleDistanceToCorners, 2) / 1);
  }

  /// Creates a BitmapDescriptor from an IconData
  Future<BitmapDescriptor> createBitmapDescriptorFromIconData(IconData iconData, Color iconColor, Color circleColor, Color backgroundColor) async {
    final pictureRecorder = PictureRecorder();
    final canvas = Canvas(pictureRecorder);

    _paintCircleFill(canvas, backgroundColor);
    _paintCircleStroke(canvas, circleColor);
    _paintIcon(canvas, iconColor, iconData);

    final picture = pictureRecorder.endRecording();
    final image = await picture.toImage(_markerSize.round(), _markerSize.round());
    final bytes = await image.toByteData(format: ImageByteFormat.png);

    return BitmapDescriptor.fromBytes(bytes.buffer.asUint8List());
  }

  /// Paints the icon background
  void _paintCircleFill(Canvas canvas, Color color) {
    final paint = Paint()
      ..style = PaintingStyle.fill
      ..color = color;
    canvas.drawCircle(Offset(_circleOffset, _circleOffset), _fillCircleWidth, paint);
  }

  /// Paints a circle around the icon
  void _paintCircleStroke(Canvas canvas, Color color) {
    final paint = Paint()
      ..style = PaintingStyle.stroke
      ..color = color
      ..strokeWidth = _circleStrokeWidth;
    canvas.drawCircle(Offset(_circleOffset, _circleOffset), _outlineCircleWidth, paint);
  }

  /// Paints the icon
  void _paintIcon(Canvas canvas, Color color, IconData iconData) {
    final textPainter = TextPainter(textDirection: TextDirection.ltr);
    textPainter.text = TextSpan(
        text: String.fromCharCode(iconData.codePoint),
        style: TextStyle(
          letterSpacing: 0.0,
          fontSize: _iconSize,
          fontFamily: iconData.fontFamily,
          package: iconData.fontPackage,
          color: color,
        ));
    textPainter.layout();
    textPainter.paint(canvas, Offset(_iconOffset, _iconOffset));
  }
}

Edit
If you want to use an asset instead, this class may be helpful

class Common {
  static Future<Uint8List> getBytesFromAsset(String path, int width) async {
    ByteData data = await rootBundle.load(path);
    ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(), targetWidth: width);
    ui.FrameInfo fi = await codec.getNextFrame();
    return (await fi.image.toByteData(format: ui.ImageByteFormat.png)).buffer.asUint8List();
  }
}

Then to create a bitmapIcon, you'll do like this

var bytes = await Common.getBytesFromAsset("your/asset/path", 100);
BitmapDescriptor.fromBytes(bytes);
Share:
846
Flavio
Author by

Flavio

Updated on December 26, 2022

Comments

  • Flavio
    Flavio over 1 year

    I'm new in Flutter, and I am trying to create a custom marker for google maps. So far i have been able to create a circle with stroke, background color and icon from a canvas, but I need to add a background image from assets instead of the icon. This is my code:

      Future<BitmapDescriptor> createCustomMarkerBitmap({IconData iconData, Color iconColor, Color fillColor, Color strokeColor}) async {
        ByteData data = await rootBundle.load('assets/images/iconTry.png');
        Uint8List lst = new Uint8List.view(data.buffer);
        Codec codec = await ui.instantiateImageCodec(lst);
        FrameInfo frame = await codec.getNextFrame();
        final pictureRecorder = PictureRecorder();
        final canvas = Canvas(pictureRecorder); 
        paintCircleFill(canvas, fillColor);
        paintCircleStroke(canvas, strokeColor);
        paintIcon(canvas, iconColor, iconData);
        paintBackgroundImage(canvas, frame.image);
        final picture = pictureRecorder.endRecording();
        final image = await picture.toImage(markerSize, markerSize);
        final bytes = await image.toByteData(format: ImageByteFormat.png);
        BitmapDescriptor.fromBytes(bytes.buffer.asUint8List());
    

    }

     void paintCircleFill(Canvas canvas, Color color) {
        final paint = Paint()
          ..style = PaintingStyle.fill
          ..color = color;
        canvas.drawCircle(Offset(circleOffset, circleOffset), innerRadius, paint);
      }
    
      void paintCircleStroke(Canvas canvas, Color color) {
        final paint = Paint()
          ..style = PaintingStyle.stroke
          ..color = color
          ..strokeWidth = (stroke);
        canvas.drawCircle(Offset(circleOffset, circleOffset), outerRadius, paint);
      }
    
    
    void paintIcon(Canvas canvas, Color color, IconData iconData) {
        final textPainter = TextPainter(textDirection: TextDirection.ltr);
        textPainter.text = TextSpan(
            text: String.fromCharCode(iconData.codePoint),
            style: TextStyle(
              letterSpacing: 0.0,
              fontSize: iconSize,
              fontFamily: iconData.fontFamily,
              color: color,
            ));
        textPainter.layout();
        textPainter.paint(canvas, Offset(iconOffset, iconOffset));
      }
    
    • dm_tr
      dm_tr over 3 years
      Can you post your whole function ?
    • Flavio
      Flavio over 3 years
      @dm_tr That is the last part where i use the canvas final picture = pictureRecorder.endRecording(); final image = await picture.toImage(markerSize, markerSize); final bytes = await image.toByteData(format: ImageByteFormat.png); BitmapDescriptor.fromBytes(bytes.buffer.asUint8List());
    • dm_tr
      dm_tr over 3 years
      Check the answer below
  • Flavio
    Flavio over 3 years
    But in this way I can only use already existings icons right? I can't import an image from assets
  • Flavio
    Flavio over 3 years
    Interesting. Is there a way to combine assets with canvas so that I can have my circle with colored borders and insiste my image?
  • Flavio
    Flavio over 3 years
    Ok, as I was expecting. Thank you very much!
  • MSaudi
    MSaudi about 3 years
    @dm_tr how to remove the border circle around the icon and keep the info windows touching the core icon in correct position ?