fluttergoogle-maps-flutter

Custom map marker icons are pixelated on physical devices


On Android and iOS, my custom map markers (PNG files) are pixelated on the google_maps_flutter map on physical devices. They are fine on simulators. Why is this?

Code:

Client usage:

if (kIsWeb) {
  return BitmapDescriptor.fromAssetImage(
    createLocalImageConfiguration(
      context,
      size: selected ? const Size(50, 58.2035) : const Size(35, 40.7422),
    ),
    filePath,
  );
} else {
  return getBitmapDescriptorFromAssetBytes(
    filePath,
    selected ? 150 : 100,
  );
}

Methods called from above:

Future<BitmapDescriptor> getBitmapDescriptorFromAssetBytes(
  String path,
  int height,
) async {
  final Uint8List imageData = await getBytesFromAsset(path, height);
  return BitmapDescriptor.fromBytes(imageData);
}

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

When I use the web code block from the client usage above (BitmapDescriptor.fromAssetImage) on Android and iOS, the map markers are not pixelated, but they are way too big, and do not get smaller when changing the size.

This answer talks about making it less pixelated by doing:

  final ratio = ui.window.devicePixelRatio.ceil();
  final height1 = height.ceil() * ratio;

And I did that, but it didn't become less pixelated, perhaps because he used SVG's and I am using PNG.


Solution

  • Do it this way, drawing on the canvas with a medium quality filter, but you need both dimensions of the image (width x height).

    Future<BitmapDescriptor> getBitmapDescriptorFromAssetBytes(
      String path,
      double width,
      double height,
    ) async {
      var imageFile = await rootBundle.load(path);
      var pictureRecorder = ui.PictureRecorder();
      var canvas = Canvas(pictureRecorder);
      var imageUint8List = imageFile.buffer.asUint8List();
      var codec = await ui.instantiateImageCodec(imageUint8List);
      var imageFI = await codec.getNextFrame();
    
      //Set the filter for the image, the default is low.
      paintImage(
          canvas: canvas,
          rect: Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()),
          image: imageFI.image,
          filterQuality: FilterQuality.medium);
    
      var image = await pictureRecorder
          .endRecording()
          .toImage(width.toInt(), height.toInt());
      var data = await image.toByteData(format: ui.ImageByteFormat.png);
      return BitmapDescriptor.fromBytes(data!.buffer.asUint8List());
    }