image_pickerで取ってきた画像をCustomPaintで表示する

# はじめに
image_pickerを使えば簡単に画像を取ってこれるのですが、
それをCustomPaintを使って表示する方法でとても手こずってしまったので備忘録を兼ねて記して置こうと思います。
CustomPaintを使えると、画像の上に描画もできるので画像の編集とかにも使えそうですね！

# 使うライブラリ
- [image_picker](https://pub.dev/packages/image_picker)

# ソースコード全体
```dart:main.dart
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final picker = ImagePicker();
  bool _imgTaken = false;
  ui.Image image;

  Future<void> _takePicture() async {
    final imageFile = await picker.getImage(
      source: ImageSource.camera,
    );
    if (imageFile == null) {
      return;
    }
    final imageByte = await imageFile.readAsBytes();
    image = await decodeImageFromList(imageByte);
    setState(() {
      _imgTaken = true;
    });
  }

  Future<void> _getImageFromGallery() async {
    final imageFile = await picker.getImage(
      source: ImageSource.gallery,
    );
    if (imageFile == null) {
      return;
    }
    final imageByte = await imageFile.readAsBytes();
    image = await decodeImageFromList(imageByte);
    setState(() {
      _imgTaken = true;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Container(
        padding: EdgeInsets.all(10),
        child: Column(
          children: [
            _imgTaken
                ? FittedBox(
                    child: SizedBox(
                      width: image.width.toDouble(),
                      height: image.height.toDouble(),
                      child: CustomPaint(
                        painter: OriginalPainter(image),
                      ),
                    ),
                  )
                : Container(
                    width: 300,
                    height: 500,
                    alignment: Alignment.center,
                    decoration: BoxDecoration(
                      border: Border.all(width: 1, color: Colors.grey),
                    ),
                    child: Text(
                      '画像が選択されていません',
                      textAlign: TextAlign.center,
                    ),
                  ),
            Row(
              children: [
                Expanded(
                  child: FlatButton.icon(
                    icon: Icon(Icons.photo_camera),
                    label: Text('カメラ'),
                    textColor: Theme.of(context).primaryColor,
                    onPressed: _takePicture,
                  ),
                ),
                Expanded(
                  child: FlatButton.icon(
                    icon: Icon(Icons.photo_library),
                    label: Text('ギャラリー'),
                    textColor: Theme.of(context).primaryColor,
                    onPressed: _getImageFromGallery,
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

class OriginalPainter extends CustomPainter {
  final ui.Image image;
  OriginalPainter(this.image);

  @override
  void paint(ui.Canvas canvas, Size size) {
    final paint = Paint();
    if (image != null) {
      canvas.drawImage(image, Offset(0, 0), paint);
    }
    paint.color = Colors.red;
    canvas.drawCircle(Offset(size.width / 2, size.height / 2), 50, paint);
  }

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

```

## image_pickerで画像を取ってくる
```dart:image_picker使って画像を取得
  final picker = ImagePicker();
  bool _imgTaken = false;
  ui.Image image;

  Future<void> _takePicture() async {
    final imageFile = await picker.getImage(
      source: ImageSource.camera,
    );
    if (imageFile == null) {
      return;
    }
    final imageByte = await imageFile.readAsBytes();
    image = await decodeImageFromList(imageByte);
    setState(() {
      _imgTaken = true;
    });
  }

  Future<void> _getImageFromGallery() async {
    final imageFile = await picker.getImage(
      source: ImageSource.gallery,
    );
    if (imageFile == null) {
      return;
    }
    final imageByte = await imageFile.readAsBytes();
    image = await decodeImageFromList(imageByte);
    setState(() {
      _imgTaken = true;
    });
  }
```
image_pickerって非常に便利ですよね。ソースを指定すればカメラ、ギャラリーどちらからでも画像を取ってこれます。
ただし、image_pickerで取ってくる画像の型は```File```になります。
そのため、```CustomPaint```で使う```ui.Image```に変換しないといけません。

```dart:Fileをui.Imageに変換
final Uint8List imageByte = await imageFile.readAsBytes();
image = await decodeImageFromList(imageByte);
```
一度、```Uint8List```に変えてからデコードする形で```ui.Image```に変換します。
また、画像が選択されたかどうかを伝えるために```setState()```で```_imgTaken```をtrueにしています。

## UIでの描画部分
```dart:Widget部分
_imgTaken
    ? FittedBox(
        child: SizedBox(
          width: image.width.toDouble(),
          height: image.height.toDouble(),
          child: CustomPaint(
            painter: OriginalPainter(image),
          ),
        ),
      )
    : Container(
        width: 300,
        height: 500,
        alignment: Alignment.center,
        decoration: BoxDecoration(
          border: Border.all(width: 1, color: Colors.grey),
        ),
        child: Text(
          '画像が選択されていません',
          textAlign: TextAlign.center,
        ),
      ),
```

表示部分では```_imgTaken```で条件分岐させています。
画像表示部分であるCustomPaint自体はサイズを持たないので、```Container```なり、```SizedBox```なりでサイズを与えてあげる必要があります。```ui.Image```であれば、すぐにwidthとheightを取ってこれるので楽ですね。

## CustomPainter
```dart:画像描画部分
class OriginalPainter extends CustomPainter {
  final ui.Image image;
  OriginalPainter(this.image);

  @override
  void paint(ui.Canvas canvas, Size size) {
    final paint = Paint();
    if (image != null) {
      canvas.drawImage(image, Offset(0, 0), paint);
    }
    paint.color = Colors.red;
    canvas.drawCircle(Offset(size.width / 2, size.height / 2), 50, paint);
  }

  @override
  bool shouldRepaint(covariant OriginalPainter oldDelegate) => false;
}
```
画像の描画はこのようになります。意外とシンプルです。
image_pickerで取ってきて変換した```ui.Image```を```canvas.drawImage```で表示することができます。
内部の```Offset```で画像の表示位置を変更することができます。(0,0)で指定すると画面左上が始点になります。
さらに今回は```canvas.drawCircle```で画像の中心に円も描いております。

また、```shouldRepaint```は画像の再描画が必要な場合はtrueにする必要があります。
お絵描きアプリなんかだと、再描画が必要になりそうですね。

# デモ
こんな感じで動きます。
![img_pkr_demo2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271414/eae46794-1e8e-6eed-b6aa-5f63b54bf26c.gif)

# おわりに
Flutterで色々できることが増えてくると非常に面白いですよね！
https://www.youtube.com/watch?v=yyHhloFMNNA&t
こんな感じでCustomPaintを使ったお絵描きアプリみたいなデモもあるので、今後はそれにも挑戦してみたいと思います！