Home » Android » android – Flutter – How to draw an Image on Canvas using DrawImage method

android – Flutter – How to draw an Image on Canvas using DrawImage method

Posted by: admin June 15, 2020 Leave a comment

Questions:

I’m trying to draw an image file into the canvas to compose my widget in Flutter.

I did follow canvas documentation but a did not success. O Image docs, thay say that:

To obtain an Image object, use instantiateImageCodec.

I did try use instantiateImageCodec method, but i just get a Codec instance, not a Image

How is the right way to get an instance of ui.Image to draw on canvas using canvas.drawImage

Here is a snnipet of my code:

Future<ui.Codec> _loadImage(AssetBundleImageKey key) async {
  final ByteData data = await key.bundle.load(key.name);
   if (data == null)
  throw 'Unable to read data';
   return await ui.instantiateImageCodec(data.buffer.asUint8List());
}

final Paint paint = new Paint()
  ..color = Colors.yellow
  ..strokeWidth = 2.0
  ..strokeCap = StrokeCap.butt
  ..style = PaintingStyle.stroke;

var sunImage = new ExactAssetImage("res/images/grid_icon.png");

sunImage.obtainKey(new ImageConfiguration()).then((AssetBundleImageKey key){
  _loadImage(key).then((ui.Codec codec){
    print("frameCount: ${codec.frameCount.toString()}");
    codec.getNextFrame().then((info){
      print("image: ${info.image.toString()}");
      print("duration: ${info.duration.toString()}");
      canvas.drawImage(info.image, size.center(Offset.zero), paint);
    });
  });
});
How to&Answers:

ui.Codec has a method getNextFrame() which returns a Future<FrameInfo> (you should probably have logic around how many frames but if you know it’s always a normal picture you could skip that.) FrameInfo has an image property which is the Image you need.

EDIT: looking at the code you have in the post, it’s not clear where you’re doing what. Is that all defined within the CustomPainter.paint method? If so, you’d definitely have issues because you can only use the canvas for the duration of the paint call; you should not retain any references to it outside of the function (which would include any asynchronous call).

I’d recommend using a FutureBuilder so that you only draw on the canvas once you’ve added the image.

That would look something like this:

Future<Image> _loadImage(AssetBundleImageKey key) async {
  final ByteData data = await key.bundle.load(key.name);
  if (data == null)
    throw 'Unable to read data';
  var codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
  // add additional checking for number of frames etc here
  var frame = await codec.getNextFrame();
  return frame.image;
}

new FutureBuilder<Image>(
  future: loadImage(....), // a Future<String> or null
  builder: (BuildContext context, AsyncSnapshot<Image> snapshot) {
    switch (snapshot.connectionState) {
      case ConnectionState.waiting: return new Text('Image loading...');
      default:
        if (snapshot.hasError)
          return new Text('Error: ${snapshot.error}');
        else
          // ImageCanvasDrawer would be a (most likely) statless widget
          // that actually makes the CustomPaint etc
          return new ImageCanvasDrawer(image: snapshot.data)
    }
  },
)

Answer:

This simple utility method returns a Future<UI.Image> given the image asset’s path:

import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as UI;

import 'package:flutter/services.dart';

Future<UI.Image> loadUiImage(String imageAssetPath) async {
  final ByteData data = await rootBundle.load(imageAssetPath);
  final Completer<UI.Image> completer = Completer();
  UI.decodeImageFromList(Uint8List.view(data.buffer), (UI.Image img) {
    return completer.complete(img);
  });
  return completer.future;
}

Answer:

class ImagePainter extends CustomPainter {
  List<ui.Image> images = new List<ui.Image>();
  ImagePainter(
      {Key key,
      @required this.noOfSlice,
      @required this.images,
      @required this.rotation,
      this.boxfit = BoxFit.contain})
      :
        // : path = new Path()
        //     ..addOval(new Rect.fromCircle(
        //       center: new Offset(75.0, 75.0),
        //       radius: 40.0,
        //     )),
        tickPaint = new Paint() {
    tickPaint.strokeWidth = 2.5;
  }
  final int noOfSlice;
  final tickPaint;
  final BoxFit boxfit;
  ui.ImageByteFormat img;
  ui.Rect rect, inputSubrect, outputSubrect;
  Size imageSize;
  FittedSizes sizes;
  double radius,
      rotation = 0.0,
      _x,
      _y,
      _angle,
      _radiun,
      _baseLength,
      _imageCircleradius,
      _incircleRadius,
      _imageOffset = 0.0,
      _imageSizeConst = 0.0;

  @override
  void paint(ui.Canvas canvas, ui.Size size) {
    print("image data:: $images");
    radius = size.width / 2;
    _angle = 360 / (noOfSlice * 2.0);
    _radiun = (_angle * pi) / 180;
    _baseLength = 2 * radius * sin(_radiun);
    _incircleRadius = (_baseLength / 2) * tan(_radiun);
    if (noOfSlice == 4) {
      _imageOffset = 30.0;
      _imageSizeConst = 30.0;
      _x = 8.60;
      _y = 4.10;
    } else if (noOfSlice == 6) {
      _imageOffset = 20.0;
      _x = 10.60;
      _y = 5.60;
    } else if (noOfSlice == 8) {
      _imageOffset = 40.0;
      _imageSizeConst = 30.0;
      _x = 12.90;
      _y = 6.60;
    }

    //print("circle radisu:: $_incircleRadius");

    canvas.save();
    canvas.translate(size.width / 2, size.height / 2);
    canvas.rotate(-rotation);
    int incr = 0;
    rect = ui.Offset((size.width / _x), size.width / _y) & new Size(0.0, 0.0);

    imageSize = new Size(size.width * 1.5, size.width * 1.5);
    sizes = applyBoxFit(
        boxfit,
        imageSize,
        new Size(size.width / 2 * .50 + _incircleRadius * .8,
            size.width / 2 * .50 + _incircleRadius * .8));
    inputSubrect =
        Alignment.center.inscribe(sizes.source, Offset.zero & imageSize);
    outputSubrect = Alignment.center.inscribe(sizes.destination, rect);
    if (images.length == noOfSlice && images.isNotEmpty)
      for (var i = 1; i <= noOfSlice * 2; ++i) {
        if (i % 2 != 0) {
          canvas.drawLine(
            new Offset(0.0, 0.0),
            new Offset(0.0, size.width / 2 - 4.2),
            tickPaint,
          );
        } else {
          canvas.save();
          canvas.translate(-0.0, -((size.width) / 2.2));
          ui.Image image = images[incr];
          if (image != null) {
            canvas.drawImageRect(
                image, inputSubrect, outputSubrect, new Paint());
          }

          canvas.restore();
          incr++;
        }
        canvas.rotate(2 * pi / (noOfSlice * 2.0));
      }
    canvas.restore();
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return false;
  }
}