コムセント 技術情報

  1. TOP
  2. コムセント 技術情報
  3. Flutterで写真を正方形の状態で撮る

Flutterで写真を正方形の状態で撮る

作成日:2023/09/29
最終更新日:2023/09/29

環境

※動作確認はAndroid実機Pixel 7でのみ行いました。
flutter:3.13.1
camera:0.10.5+4
https://pub.dev/packages/camera
image 4.1.2
https://pub.dev/packages/image

はじめに

今回は、Flutterで写真撮影をする際に、正方形の状態で撮影する方法を紹介します。

こちらが完成したアプリの画像です。

CameraPreview自体を切り抜いたり変形させる手段もありますが、今回はこのようにContainerを上に重ねて、撮影後に自動で切り抜く疑似的なプレビューを作成します。

CameraPreviewを切り抜いたり変形させたりするコードも試してみたのですが、私の環境だとカメラのプレビュー自体が横につぶれてしまったりして、正方形に切り抜いたプレビューというのができませんでした。

そのため、うまく切り抜けたStackで重ねる方法でやっていきます。

CameraControllerのinitialize

まず、CameraControllerのinitializeをします。
https://pub.dev/documentation/camera/latest/camera/CameraController-class.html

await _cameraController!.initialize();

その後に、カメラの向きを固定します。

await _cameraController!.lockCaptureOrientation(DeviceOrientation.portraitUp);

カメラのプレビューを表示

カメラのプレビューを表示すること自体はとても簡単です。

CameraPreview(_cameraController!)

これだけで、プレビューを表示してくれます。

下半分を隠すContainerを重ねる

上記でカメラのプレビューはできたので、あとは下半分を隠してシャッターボタンを置くだけです。

final previewWidth = MediaQuery.of(context).size.width;
final previewHeight = MediaQuery.of(context).size.height;
Stack(
  children: [
// 全体の高さ用
    SizedBox(
      height: previewHeight-kToolbarHeight,
    ),
// カメラのプレビュー
    SizedBox(
      width: previewWidth,
      child: CameraPreview(_cameraController!)
    ),
//下半分隠し+シャッターボタン
    Positioned(
      top: _cameraController!.value.previewSize!.width / _cameraController!.value.previewSize!.aspectRatio,
      child: Container(
        alignment: Alignment.topCenter,
        width: _cameraController!.value.previewSize!.width / _cameraController!.value.previewSize!.aspectRatio + 10,
        height: _cameraController!.value.previewSize!.width / _cameraController!.value.previewSize!.aspectRatio,
        child: Center(
          child: SizedBox(
            width: 80,
            height: 80,
            child: ElevatedButton(
              style: ElevatedButton.styleFrom(
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(50.0),
                ),
             ),
             onPressed: () async{
               XFile photo = await _cameraController!.takePicture();
             },
             child: Icon(
               Icons.camera
             ),
           ),
         ),
       ),
     ),
   ),
  ]
),

このようにStackでWidgetを重ねれば、正方形の見た目のカメラのページが作成できます。

下半分隠しのPositionedのtopやWidth、Heightは、それぞれのバージョンにあった仕様に変更してください。
検索するといろいろな計算方法がでてきますので、作成したい位置にくるように調節してください。

正方形の写真にトリミングする

シャッターボタンのonPressedは、このままだと普段通り長方形の写真が撮れてしまうので、正方形に切り取ります。

onPressed: () async{
  const saveWidth = 500;
  String path = '${(await getTemporaryDirectory()).path}/${DateTime.now().millisecondsSinceEpoch}.png';
  
  XFile takedPhoto = await _cameraController!.takePicture();
  final decodeImage = img.decodeImage(
    await File(takedPhoto.path).readAsBytes()
  )!;
  final resizedImage = img.copyResize(
    decodeImage,
    width: saveWidth
  );
  Image croppedImage = img.copyCrop(
                                resizedImage,
                                x: 0,
                                y: 0,
                                width: saveWidth,
                                height: saveWidth
                            );
}

takePictureで撮影した写真を、imageパッケージでdecode, cropしています。
私の環境ではimageが重複していたので as img にしています。

これで左上からsaveWidthのサイズの正方形に切り取ることができました。

このcroppedImageをriverpod等の状態管理パッケージで管理してフォームに戻れば、切り取った状態の写真をアップロードできます。

まとめ

この知識を応用すれば、ClipRectしたContainerを重ねて好きな形に切り抜くことも可能ですね。
暇つぶし用に紙を切り抜くクラフトパンチができるだけのアプリを作ってみてもいいかもしれません。

上記のonPressed内ではエラーハンドリングやデータの保存、この後の動作を省略しています。
適切なところでtry-catchで囲う等してエラーハンドリングをしてください。

私が悩んでいる最中は、どこがエラーになっているか全くわからず1つ1つtry catchしてdebugPrintしていました・・・。

プログラマー/A.A

このメンバーの記事一覧へ

おすすめ記事