26
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Flutter でWidgetから画像を生成してロゴ画像を追加する

Last updated at Posted at 2020-12-14

概要

LINEの友達登録のQRコードや、○○payの送金先などアプリ内で作成したプロフィールや画像、ゲームのスコア、QRコードなどを共有したい場面が出てくると思います。
そういった場面で使用できる方法を紹介したいと思います。
Widgetから画像を生成するだけなら簡単で、「Widgetから画像を生成する」を見るとほぼほぼ実装できると思います。

実装

ここからFlutter での実装を説明していきます。

1. Widgetから画像を生成する

ウィジェットから画像を生成するには、画像として書き出したいウィジェットをRepaintBoundaryで囲み、RepaintBoundaryのKeyからRenderObjectを取得することで、画像を生成することができます。

final _globalKey = GlobalKey();

Widget build(BuildContext context) {
  return RepaintBoundary(
    key: _globalKey,
    child: SafeArea(
      child: ListView.builder(
        itemCount: 40,
        itemBuilder: _buildRow,
      ),
    ),
  );
}

// RepaintBoundary の key を渡す
Future<Uint8List> convertWidgetToImage(GlobalKey widgetGlobalKey) async {
  // RenderObjectを取得
  RenderRepaintBoundary boundary = widgetGlobalKey.currentContext.findRenderObject();
  // RenderObject を dart:ui の Image に変換する
  ui.Image image = await boundary.toImage();
  ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
  return byteData.buffer.asUint8List();
}

上記のコードで下記のように画像を生成することができます。

画像に変換したものはImage.memory()を使用することでWidgetとして表示することも可能です。

上の画像を見ていただきたいのですが、生成した画像の下の方を見ると「範囲内」の文字が切れているのがわかります。
この書き方では画面の描画範囲外に出ているWidgetを画像に含めることができません。
この後に画面の描画範囲外にあるWidgetも含めて画像を生成するコードを紹介します。

2. 画面の描画範囲外にあるWidgetも含め画像を生成する

ListViewなどを使用していると描画範囲外にWidgetがあることが多々あると思います。
それらのWidgetを含めて画像を生成するにはRepaintBoundarySingleChildScrollViewで囲ってあげる必要があります。
「Widgetから画像を生成する」のサンプルコードを変更すると下記のようになります。

final _globalKey = GlobalKey();

Widget build(BuildContext context) {
  return SingleChildScrollView(
    primary: true,
    child: RepaintBoundary(
      key: _globalKey,
      child: SafeArea(
        child: ListView.builder(
          primary: false,
          shrinkWrap: true,
          itemCount: 40,
          itemBuilder: _buildRow,
        ),
      ),
    ),
  );
}

コードを変更して生成した画像は下記のようになります。
これで、画像を生成したい範囲内のWidgetをすべて含めることができました。
次は右下にロゴマークを追加したいと思います。

3. 右下にロゴを埋め込み画像を生成する

Widget の変更はありません、変更があるのは画像生成のコードです。
2つの画像から1つの画像を生成するので、Canvas を使用して画像を生成します。
Widget からui.Image を作るまでは同じです。
ここでは、PictureRecorder、Canvas、CustomPainterを使用します。

convert_to_image.dart
class ConvertWidgetToImage {
  Future<Uint8List> execute(GlobalKey widgetGlobalKey) async {
    RenderRepaintBoundary boundary = widgetGlobalKey.currentContext.findRenderObject();
    ui.Image image = await boundary.toImage();
    // Canvasを用意
    final ui.PictureRecorder recorder = ui.PictureRecorder();
    final Canvas canvas = Canvas(recorder);
    // assets の画像を読み込む
    final logoImage = await loadLocalImage();

    // Canvasで2つの画像を合成する
    ImagePainter(image, logo: logoImage)..paint(canvas, Size.infinite);
    // Canvas に描いたものを画像にする
    final ui.Image renderImage = await recorder.endRecording().toImage(
          image.width, image.height + logoImage.height,
        );
    ByteData byteData = renderImage.toByteData(format: ui.ImageByteFormat.png);
    return byteData.buffer.asUint8List();
  }

  Future<ui.Image> loadLocalImage() async {
    Completer<ImageInfo> completer = Completer();

    /// ImageProvider をui.Imageに変換している
  AssetImage('assets/logo.png').resolve(ImageConfiguration()).addListener(
          (ImageStreamListener((ImageInfo info, bool _) => completer.complete(info))),
        );
    ImageInfo imageInfo = await completer.future;
    return imageInfo.image;
  }
}
image_painter.dart
class ImagePainter extends CustomPainter {
  /// Widgetから生成した画像
  final ui.Image image;

  /// 下部に追加するロゴ
  final ui.Image logo;

  ImagePainter(
    this.image, {
    this.logo,
  });

  @override
  void paint(Canvas canvas, Size size) {
    // canvas の左上に画像にしたいWidgetを配置する

    canvas.drawImage(image, Offset(0, 0), Paint());

    if (logo != null) {
      // canvas の右下にロゴを配置する
      canvas.drawImage(
          logo,
          Offset(image.width.toDouble() - logo.width.toDouble(),
              image.height.toDouble() - logo.height.toDouble()),
          Paint());
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}

上記のコードで右下にロゴを追加した画像が生成出来ました。

ロゴの位置はOffsetの値を変更することで位置を変えることができます。

あとがき

投稿の日を1日間違えていて焦って書いたので、殴り書きになってしまいましたが上記の方法でWidgetから画像の生成ができます。
特に難しいことをしないで画像が生成できて便利ですね。
ちなみにShareプラグイン等を使用して画像を共有するには一度ローカルに保存する必要があるので、Tempディレクトリにでも保存して共有するようにしてください。

あとがきも投稿してから後日に書いています。次事は間に合うように書きます

26
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?