8
2

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.

Global WalkersAdvent Calendar 2020

Day 10

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

Last updated at Posted at 2020-12-10

はじめに

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を使ったお絵描きアプリみたいなデモもあるので、今後はそれにも挑戦してみたいと思います!

8
2
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
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?