🌁

image_cropperを使ってみた

2025/02/12に公開

Image Cropper デモアプリ

Flutterで画像を回転したり切り取るトリミングをできるデモアプリを作ってみた。
今回は、iOSのみで動作検証してます🙇

https://youtube.com/shorts/AnYwzHp1xyc

完成品

使用したライブラリ

https://pub.dev/packages/extended_image
https://pub.dev/packages/image_cropper
https://pub.dev/packages/image_picker

example
main.dart
import 'package:flutter/material.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Image Editor Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const ImageEditorPage(),
    );
  }
}

class ImageEditorPage extends StatefulWidget {
  const ImageEditorPage({super.key});

  
  State<ImageEditorPage> createState() => _ImageEditorPageState();
}

class _ImageEditorPageState extends State<ImageEditorPage> {
  File? _image;
  final _picker = ImagePicker();
  String? _errorMessage;

  Future<void> _pickImage() async {
    try {
      final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
      if (image != null) {
        setState(() {
          _image = File(image.path);
          _errorMessage = null;
        });
        _cropImage();
      }
    } catch (e) {
      setState(() {
        _errorMessage = 'Failed to load image. Please try another image.';
        _image = null;
      });
    }
  }

  Future<void> _cropImage() async {
    if (_image != null) {
      try {
        final croppedFile = await ImageCropper().cropImage(
          sourcePath: _image!.path,
          aspectRatioPresets: [
            CropAspectRatioPreset.square,
            CropAspectRatioPreset.ratio3x2,
            CropAspectRatioPreset.original,
            CropAspectRatioPreset.ratio4x3,
            CropAspectRatioPreset.ratio16x9
          ],
          uiSettings: [
            AndroidUiSettings(
              toolbarTitle: 'Edit Image',
              toolbarColor: Colors.blue,
              toolbarWidgetColor: Colors.white,
              initAspectRatio: CropAspectRatioPreset.original,
              lockAspectRatio: false,
              hideBottomControls: false,
            ),
            IOSUiSettings(
              title: 'Edit Image',
              cancelButtonTitle: 'Cancel',
              doneButtonTitle: 'Done',
              rotateClockwiseButtonHidden: false,
              hidesNavigationBar: false,
              rotateButtonsHidden: false,
              aspectRatioPickerButtonHidden: false,
              aspectRatioLockEnabled: false,
              resetAspectRatioEnabled: true,
              showCancelConfirmationDialog: true,
            ),
          ],
        );

        if (croppedFile != null) {
          setState(() {
            _image = File(croppedFile.path);
            _errorMessage = null;
          });
        }
      } catch (e) {
        setState(() {
          _errorMessage = 'Failed to process image. Please try another image.';
          _image = null;
        });
      }
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Image Editor Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            if (_errorMessage != null)
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: Text(
                  _errorMessage!,
                  style: const TextStyle(color: Colors.red),
                  textAlign: TextAlign.center,
                ),
              ),
            if (_image != null)
              Container(
                margin: const EdgeInsets.all(16),
                child: Image.file(
                  _image!,
                  width: 300,
                  height: 300,
                  fit: BoxFit.contain,
                ),
              )
            else if (_errorMessage == null)
              const Text('Please select an image'),
            const SizedBox(height: 20),
            if (_image != null)
              ElevatedButton(
                onPressed: _cropImage,
                child: const Text('Edit Image'),
              ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _pickImage,
        tooltip: 'Select Image',
        child: const Icon(Icons.add_photo_alternate),
      ),
    );
  }
}

Image Cropperとは

Image Cropperは、Flutterアプリケーションで画像のトリミング機能を実装するための強力なプラグインです。
ネイティブの画像編集機能を活用し、高品質な画像トリミング体験を提供します。

主な特徴

  • 🎯 直感的なUIでの画像トリミング
  • 🔄 回転・反転機能
  • 📐 アスペクト比の固定オプション
  • 🎨 カスタマイズ可能なUIテーマ
  • 💪 Android/iOS両プラットフォームでのネイティブ実装

セットアップ方法

1. 依存関係の追加

pubspec.yamlに以下を追加します:

dependencies:
  extended_image: ^8.2.0
  image_cropper: ^5.0.1
  image_picker: ^1.0.7

2. プラットフォーム固有の設定

iOS

ios/Runner/Info.plistに以下の権限を追加:

<key>NSPhotoLibraryUsageDescription</key>
<string>写真を選択してトリミングするために許可が必要です。</string>
<key>NSCameraUsageDescription</key>
<string>写真を撮影してトリミングするために許可が必要です。</string>
Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>$(DEVELOPMENT_LANGUAGE)</string>
	<key>CFBundleDisplayName</key>
	<string>Image Croper Demo</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>image_croper_demo</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>$(FLUTTER_BUILD_NAME)</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleVersion</key>
	<string>$(FLUTTER_BUILD_NUMBER)</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UISupportedInterfaceOrientations~ipad</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationPortraitUpsideDown</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>CADisableMinimumFrameDurationOnPhone</key>
	<true/>
	<key>UIApplicationSupportsIndirectInputEvents</key>
	<true/>
	<key>NSPhotoLibraryUsageDescription</key>
	<string>写真を選択して編集するために、フォトライブラリへのアクセスが必要です。</string>
</dict>
</plist>

Android

Android 10以上で動作させるには、android/app/src/main/AndroidManifest.xmlに以下を追加:

<activity
    android:name="com.yalantis.ucrop.UCropActivity"
    android:screenOrientation="portrait"
    android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>

基本的な使い方

// Image Cropperを使用した画像トリミングの基本実装
Future<void> cropImage(String sourcePath) async {
  final croppedFile = await ImageCropper().cropImage(
    sourcePath: sourcePath,
    aspectRatio: CropAspectRatio(ratioX: 1.0, ratioY: 1.0),
    uiSettings: [
      AndroidUiSettings(
        toolbarTitle: '画像をトリミング',
        toolbarColor: Colors.blue,
        toolbarWidgetColor: Colors.white,
      ),
      IOSUiSettings(
        title: '画像をトリミング',
      ),
    ],
  );
  
  if (croppedFile != null) {
    // トリミングされた画像の処理
  }
}

カスタマイズオプション

トリミング設定のカスタマイズ

  • アスペクト比の指定
  • 最大出力サイズの設定
  • 圧縮品質の調整
  • 回転の有効/無効
  • トリミング形状(円形/長方形)

UIのカスタマイズ

  • ツールバーの色
  • 背景色
  • アクティブなコントロールの色
  • ステータスバーの色

ベストプラクティス

  1. 画像の選択にはimage_pickerプラグインと組み合わせて使用
  2. 大きな画像を扱う場合はmaxWidthmaxHeightを指定
  3. 用途に応じた適切なアスペクト比の設定
  4. エラーハンドリングの実装

注意点

  • 処理中の適切なローディング表示
  • メモリ使用量の考慮
  • 権限の適切な処理
  • 画像サイズの最適化

まとめ

Image Cropperは、Flutterアプリケーションで必要な画像トリミング機能を簡単に実装できる優れたソリューションです。
ネイティブの実装により、高いパフォーマンスと使いやすさを両立しています。

Discussion