1
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

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

はじめに

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

使うライブラリ

ソースコード全体

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で画像を取ってくる

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に変換しないといけません。

Fileをui.Imageに変換
final Uint8List imageByte = await imageFile.readAsBytes();
image = await decodeImageFromList(imageByte);

一度、Uint8Listに変えてからデコードする形でui.Imageに変換します。
また、画像が選択されたかどうかを伝えるためにsetState()_imgTakenをtrueにしています。

UIでの描画部分

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

画像描画部分
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.Imagecanvas.drawImageで表示することができます。
内部のOffsetで画像の表示位置を変更することができます。(0,0)で指定すると画面左上が始点になります。
さらに今回はcanvas.drawCircleで画像の中心に円も描いております。

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

デモ

こんな感じで動きます。
img_pkr_demo2.gif

おわりに

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
1
Help us understand the problem. What are the problem?