インスタ映えも! Flutter(Dart)とDjango(Python)を使って、簡単な写真加工アプリ(撮影機能あり)を作る方法

Flutterで、写真を撮影するフォームを作り、その撮影した写真をバックエンドであるPythonに送り、Pythonの画像加工の技術を使って、セピア色にするのを作っていきたいと思います。


エディターは、Android Studioを使っていきます。

![AD_4nXcbQtneDPpZqJ86SjGOnAoHMrDEj2lcfXl_gC_LE6y7ojOq58BhkvfKWH51wKPZs84uHjWxOHO_f8W8sDk7YRfpqjNgqLrtwyVzGnnLzkAxr_IhUWBXtn_k.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/16d1a835-d7b4-ed67-7727-0a27da78736e.jpeg)




Android StudioにFlutterの環境構築をするには、 こちらのサイトを参考にするといいです↓
https://note.com/qkuronekop/n/nb78b7c642d6a

Flutterの環境構築ができましたら、プロジェクトを作成していきます。

今回のプロジェクト名は、photoにします。

![AD_4nXc-RiaswPof7PwxnHc7zk-_4aSmfRd4jLVvOAwpOYy_5fydkCztSDePBVb6GdUIg2xthwHLlJGloa2CIOVnw8J-_pc0bqSgbOJHhKXu3dFW9YuNC1oEmXmb.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/f237cae9-96c3-d05c-5fed-58a881e5f0af.jpeg)




とりあえず、まずは、写真が撮影できるアプリを作成します。


実装では、以下のパッケージ


**・camera:** デバイス上のカメラを操作するのに必要
**・path provider:** 画像の保存場所を見つけてくれる
**・path:** 画像の保存先を作成してくれる
**・video player:** ビデオを再生してくれる

が必要となるので、コマンドで、

```
$ flutter pub get
$ flutter pub add path_provider
$ flutter pub add path
$ flutter pub add video_player
```
<font color=red>※ $マークは、コマンドライン(ターミナル)での入力を意味しているので、無視してください。</font>

を打ってください。

android/app/build.gradleファイルのminSdkVersionを以下のように変更します。
```diff_gradle:android/app/build.gradle
plugins {
    id "com.android.application"
    id "kotlin-android"
    id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
    localPropertiesFile.withReader('UTF-8') { reader ->
        localProperties.load(reader)
    }
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
    flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
}
android {
    namespace "com.example.photo"
    compileSdk flutter.compileSdkVersion
    ndkVersion flutter.ndkVersion
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }
    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId "com.example.photo"
        // You can update the following values to match your application needs.
        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
- minSdkVersion flutter.minSdkVersion
+ minSdkVersion 21
        targetSdkVersion flutter.targetSdkVersion
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }
    buildTypes {
        release {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now, so `flutter run --release` works.
            signingConfig signingConfigs.debug
        }
    }
}
flutter {
    source '../..'
}
dependencies {}
```
にします。

そして、カメラ機能を使う際に、許可を要求するための文言を追加します。

そのために、
ios/Runner/Info.plistを
```diff_plist:ios/Runner/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>CADisableMinimumFrameDurationOnPhone</key>
	<true/>
	<key>CFBundleDevelopmentRegion</key>
	<string>$(DEVELOPMENT_LANGUAGE)</string>
	<key>CFBundleDisplayName</key>
	<string>Photo</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>photo</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>NSCameraUsageDescription</key>
+ <string>撮影をするために、カメラ機能を使う必要があります。</string>
+ <key>NSMicrophoneUsageDescription</key>
+ <string>撮影をする際に、マイク機能を使う場合があります。</string>
	<key>UIApplicationSupportsIndirectInputEvents</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>
</dict>
</plist>
```

というふうに変更します。

次に、lib/main.dartを、

```dart:lib/main.dart

import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
Future<void> main() async {
  // Flutterのバインディングを初期化する。この関数を呼び出すことで、Flutterが完全に起動したことが保証される
  WidgetsFlutterBinding.ensureInitialized();
  // 使用可能なカメラを取得する非同期処理
  final cameras = await availableCameras();
  // 最初のカメラデバイスを取得（一般的には、デバイスに複数のカメラがある場合、最初に使うカメラを選択）
  final firstCamera = cameras.first;
  // 取得できているか確認
  print(firstCamera);
  // アプリケーションを実行する
  runApp(MyApp(camera: firstCamera));
}
class MyApp extends StatelessWidget {
  const MyApp({
    Key? key,
    required this.camera,
}) : super(key: key);
  // カメラ情報（デバイスのカメラ）
  final CameraDescription camera;
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'カメラ機能',
      theme: ThemeData(),
      home: TakePictureScreen(camera: camera),
    );
  }
}
// 写真撮影画面
class TakePictureScreen extends StatefulWidget {
  const TakePictureScreen({
    Key? key,
    required this.camera, // 使用するカメラ情報を受け取る
  }) : super(key: key);
  // カメラ情報
  final CameraDescription camera;
  @override
  TakePictureScreenState createState() => TakePictureScreenState();
}
class TakePictureScreenState extends State<TakePictureScreen> {
  late CameraController _controller; // カメラの操作を管理するコントローラー
  late Future<void> _initializeControllerFuture; // カメラの初期化を待つ
  @override
  void initState() {
    super.initState();
    // CameraControllerを初期化。使用するカメラと解像度を指定。
    _controller = CameraController(
      // 受け取ったカメラ情報を使用
    widget.camera,
      // カメラの解像度設定
      ResolutionPreset.medium,
    );
    // カメラの初期化処理を開始し、その完了を待つ
    _initializeControllerFuture = _controller.initialize();
  }
  @override
    void dispose() {
    // 画面が破棄される際にカメラコントローラーも破棄してリソースを解放
      _controller.dispose();
      super.dispose();
    }
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        body: Center(
          child: FutureBuilder<void>(
            // カメラ初期化が完了するのを待つ
            future: _initializeControllerFuture,
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.done) {
                // 初期化が完了したらカメラプレビューを表示
                return CameraPreview(_controller);
              } else {
                // 初期化中はローディングインジケーターを表示
                return const CircularProgressIndicator();
              }
            },
          ),
        ),
        // 写真を撮るためのボタン
        floatingActionButton: FloatingActionButton(
          onPressed: () async {
            // 写真を撮る（非同期処理）
            final image = await _controller.takePicture();
            // 写真を撮った後、次の画面へ遷移（撮影した画像を表示）
            await Navigator.of(context).push(
              MaterialPageRoute(
                builder: (context) => DisplayPictureScreen(imagePath: image.path),
                fullscreenDialog: true,
              ),
            );
          },
          child: const Icon(Icons.camera_alt),// カメラアイコン
        ),
      );
    }
  }
  // 撮影した写真を表示する画面
  class DisplayPictureScreen extends StatelessWidget {
    const DisplayPictureScreen({Key? key, required this.imagePath})
        : super(key: key);
    // 画像のファイルパス
    final String imagePath;
    @override
    Widget build(BuildContext context) {
      return Scaffold(
      appBar: AppBar(title: const Text('この写真でいいですか？')),
      body: Center(
        // 画面中央に撮影した画像を表示
          child: Image.file(File(imagePath))),
      );
  }
  }
```

にします。

それでは、さっそくテストをしてみましょう。

実機は、お使いのパソコンとスマホを使い、下のような専用のコードで、同期させます。

![AD_4nXfdUZDaI576EOJRf8Ihi8DYWhuVg3g1tgeh-zOXaP_zyQsukNF-ANmoY3i15OJgbYbZSGf3L5Lufl9ZGjWK7tSmtm877avSqzXAyrASXvODg7CselgFa0qX.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/9183a39f-605a-3ff5-2a40-463460bf9d94.jpeg)


今回は、iphoneを使います。

そして、この開発ツールとしてXCodeを使います。

![AD_4nXcUYSb19_c7WHBw7EMFVAgS3_f2b_sNq8i7iwRb3nlt9gL6kT2PbC_eYKdCBvr8PsiV5aZ0mPvoUth1yf2sgElo9StT2aHJvCdVEQjsexnVV5dZEtlTa6aM.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/c4c8fa75-f584-a96d-eefb-7f550aa8a421.jpeg)




XCodeの使い方やテストをする際のスマホの環境設定については、
こちらのサイトを参考にしてみてください↓

https://zenn.dev/tatukane/articles/1985d23cfae8d8

 FlutterやCocoaPodsの依存関係の影響で、テストをする際にエラーが発生する場合があります。
その場合は、
ターミナルで、
```
$ flutter clean
$ flutter pub get
```
と入力し、Flutterのキャッシュと依存関係を再取得します。
次に、iOSの依存関係を管理する CocoaPods のインストールを確認します。

CocoaPodsがインストールされていない場合、インストールする方法を以下に記述します。

**前提条件**

・macOSがインストールされていること。
・Xcodeがインストールされていること。

**CocoaPods**のインストール手順


Homebrewを使ったインストール（推奨） HomebrewはmacOS向けのパッケージマネージャーで、CocoaPodsもHomebrewを使ってインストールできます。

もしHomebrewがインストールされていない場合は、まずHomebrewをインストールしてください。
```
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```
次に、Homebrewを使ってCocoaPodsをインストールします。

```
$ brew install cocoapods
```
インストール後、podコマンドが使えるようになります。インストールが成功したことを確認するために、次のコマンドを実行してバージョンを確認できます。
```
$ pod --version
```

バージョン番号が表示されれば、インストールは正常に完了しています。

もしHomebrewを使わずにインストールしたい場合は、以下のコマンドを実行します。
```
sudo gem install cocoapods
```
インストール後、以下のコマンドでバージョンを確認できます。
```
$ pod --version
```
これでpodコマンドが使えるようになります。

CocoaPodsの初期設定

インストール後、最初にCocoaPodsをセットアップするために、以下のコマンドを実行します。
```
$ pod setup
```

これにより、CocoaPodsの依存関係を管理するためのローカルリポジトリが作成されます。この操作は初回のみに必要です。


CocoaPodsのキャッシュをクリアしてから、pod install を再度実行してみてください。
やり方は、
ターミナルで、

```
$ cd ios
$ pod cache clean --all
$ pod install
```
というコマンドを入力します。
Xcodeの設定を確認
Xcodeの設定で、iOSプロジェクトが適切にビルドされるか確認します。
Xcodeで ios/Runner.xcworkspace を開きます。

```
$ open ios/Runner.xcworkspace
```
<font color=red>※ iosディレクトリ内にいる場合は、ios/の部分は入りません。</font>

Xcodeでプロジェクトをクリーンビルドします。

メニューから **Product** > **Clean Build Folder** を選択。
その後、**Product** > **Build** を実行します。

これで、エラーが解決されるかと思います。

下の画面のように、

![AD_4nXeCMEgkUcfNMEwedu1w-FPhiu86hp0HBqdjgppZU9o2dhPdJx7xsAiCCx-3ev6D6tBeXK9ZgjaisbSYqFLGM8ezesyBcvlnfhXXJ1sSqe1lDCCgV-2pBlbb.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/c7bb5d12-3da4-ffff-a32a-b8a8c466b64a.jpeg)


(Build Succeeded)という表示が出れば、成功です。

そうなりましたら、

ターミナルで、
```
$ flutter run
```

と打ち、

![AD_4nXdHPxJ4_wYRCeZ5q9thBTeiz68-h-LoJcwO5udawGBOLcFZQrNTn4yiWnbkhEKN3xwdSBMXtFq_M-jEDqMmPyOYzEk0ewtV_pFN9CX33d64PzE7z72Vqrup.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/fabf25f1-9ac1-c595-cf8d-0cf6a8c9ce57.jpeg)

というようなメッセージが出れば、成功です。

こうしたメッセージが出ると、

パソコンと同期しているスマホの画面が、
![AD_4nXe26ka8s8hPp19dYGUD7S2cKRXbXk9yT0a-IBnQW_VW5IZE552nACEVfHmqZBOCfmUolZJC0yDvH4pFaUwTK9hBt7rAcCWMkMDM2Kvz3vYXlLcuzG_fDzB6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/542033ab-600b-d603-b4ee-d73d5e499867.jpeg)


このように、スマホに映し出され、右下のカメラボタンを押すと、

![AD_4nXejQlGy5aT6AHKRzTCV07HX9fp0n8m2KVPncPObRGHv61BfeiEk7j750vH4iAPyZfyt0SkyvXtBcH9kpcl_x0bH5TEnSrdb9Z05_fTLdr5iiU9zS-aJ10Mp.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/8b528541-cb1d-816a-bb5e-07b1a1db92d0.jpeg)

という画面に切り替われば成功です。

これが第一段階です。


これを発展させて、Djangoと連携して、ノスタルジックなセピア色の写真に変えるアプリにしていきます。

今度は、以下のパッケージが必要になります。

**・image_gallery_saver:** アプリ内で撮影した画像やダウンロードした画像を簡単にデバイスのギャラリー（画像フォルダ）に保存してくれます。
**・http:** Web サーバーとの通信や、外部 API とのやり取りが簡単に行えるようにする。
```
$ flutter pub add image_gallery_saver
$ flutter pub add http
```
と、コマンドで打ってください。

その後に、

ios/Runner/info.plistに、以下の文を追記します。
```diff_plist:ios/Runner/info.plist
	<key>NSCameraUsageDescription</key>
	<string>撮影をするために、カメラ機能を使う必要があります。</string>
	<key>NSMicrophoneUsageDescription</key>
	<string>撮影をする際に、マイク機能を使う場合があります。</string>
+ <key>NSPhotoLibraryAddUsageDescription</key>
+ <string>撮影した写真を保存します</string>
	<key>UIApplicationSupportsIndirectInputEvents</key>
	<true/>
```
これらの記載をすると、

アプリを起動させた時、

![アクセス権を求めています.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/c2ca9afb-635c-1c6c-d221-6f0df838ae78.png)

というメッセージが出ます。

こちらのターミナルで、

![AD_4nXeGkRwkERNLF7A8DZSnQqjvJdiQvq8PknPv3NdyER9FtIc5j3FIlNlHxiY1XhLWVJVRt5JMeBN2_J0CVUbnHqm5P_D8sBbKal9f5d9ppJxCYa9fDz3U66QV.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/9cd495c9-754d-325e-fbf8-fc0a72e0088e.jpeg)


アプリをまとめるディレクトリを作成していきます。

今回は、Cameraというアプリにしたいので、
```
$ mkdir Camera
```
と入力し、
```
$ cd Camera
```
で、Cameraディレクトリに入り、PythonのフレームワークであるDjangoをインストールします。

Djangoのインストールの仕方は、こちらのサイトを参考にするといいです。↓

https://libproc.com/install-django-on-macos/



Pythonで、撮影した写真を加工するためのファイルを作りたいので、

Djangoアプリを作成するために、以下のコマンドを入力します。
今回は、pythonという名前のプロジェクトを作成します。
```
$ django-admin startproject python
```
すると、ディレクトリ構造は、
```
python
    ├── manage.py
    └── python
        ├── __init__.py
        ├── asgi.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py
```
となります。

そして、この中にアプリケーションを作成します。

撮影した写真を加工するprocessというアプリケーションを作りたいので、

アプリケーションを作成するには、manage.pyと同じディレクトリに入り、
```
$python manage.py startapp process
```
というコマンドを打ってください。

すると、ディレクトリ構造は、
```
python
├── manage.py
├── process
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
└── python
    ├── __init__.py
    ├── __pycache__
    │   ├── __init__.cpython-311.pyc
    │   └── settings.cpython-311.pyc
    ├── asgi.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py
```

という構造に変わります。


パスを通すために、python/python/urls.pyに、
```py:python/python/urls.py
"""processed_photo URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.1/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
    path("admin/", admin.site.urls),
    path("process/", include("process.urls")),
]
```
に変更。

そして、processディレクトリ内に、urls.pyファイルを作り、パスを通すために、以下のコードを書きます。

```py:python/process/urls.py
from django.urls import path
from process import views
urlpatterns = [
    path("request/", views.CameraRequest, name="camera_request"),
]
```

processディレクトリの中のviews.pyに撮影した写真を送り、セピア色に加工するためのコードを書いていきます。
```py:python/process/views.py

from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.csrf import csrf_exempt
from PIL import Image
import io
def processed_with_sepia(image_file):
    # セピア色にする加工
    RGBImage = Image.open(image_file).convert("RGB")
    width, height = RGBImage.size
    sepia_image = Image.new("RGB", (width, height))
    for y in range(height):
        for x in range(width):
            r,g,b = RGBImage.getpixel((x, y))
            # セピア調の色合いに変化
            new_r = int(r * 0.393 + g * 0.769 + b * 0.189)
            new_g = int(r * 0.349 + g * 0.686 + b * 0.168)
            new_b = int(r * 0.272 + g * 0.534 + b * 0.131)
            # RGB値が255を超えないように調整
            new_r = min(new_r, 255)
            new_g = min(new_g, 255)
            new_b = min(new_b, 255)
            sepia_image.putpixel((x,y), (new_r, new_g, new_b))
        
    return sepia_image    
@csrf_exempt
def CameraRequest(request):
    if request.method == 'POST' and request.FILES.get('photo'):
        image_file = request.FILES.get('photo')
        if image_file:
            # デバッグ情報を表示するためにprint文を追加
            print('デバッグのコード:受け取った画像のファイル名 = ', image_file.name)
            # 画像を受け取って処理するコードを追加します
            processed_image = processed_with_sepia(image_file)
            
            with io.BytesIO() as output:
                processed_image.save(output,format='PNG')
                processed_image_binary = output.getvalue()
            # レスポンスとして処理済みの画像を返します
            response = HttpResponse(processed_image_binary, content_type = "image/png")
            response['Content-Disposition'] = 'attachment; filename="photo.png"'
            return response
        
    return HttpResponseBadRequest()
```       


この作業が終わると、撮影した写真をFlutterからDjangoに渡すために、

lib/main.dartのコードを書き換えていきます。

FlutterからDjangoに写真を送る際、下の画像のように、

![AD_4nXc1Co4bKvpjGSwnGX30kISZgyB2y7bLkiTuV4r7O6qI_RNZevFD_45luvVb6dzqmmPv9HFV54YSxdXgK0mrNRYy5CN5D43Fks07JDNI7bQ7kS4SYjKOnPvE.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/776da9b2-313a-5b90-52cf-07dbb03a8f05.png)

反時計回りに90度傾いた状態で、渡されてしまいます。

バックエンドであるDjango側でその画像を元の傾きに戻す処理を行うと、

画像が見切れてしまうため、


フロントエンドであるFlutter側で再度、元の傾きに戻す処理を行います。


このことを念頭に、Android Studioに戻り、

photoプロジェクト内の

lib/main.dartを以下のように書き換えます。

```dart:lib/main.dart
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'dart:ui';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:image_gallery_saver/image_gallery_saver.dart';
Future <void> main() async {
 // Flutterのバインディングを初期化する。この関数を呼び出すことで、Flutterが完全に起動したことが保証される
  WidgetsFlutterBinding.ensureInitialized();
  // 使用可能なカメラを取得する非同期処理
  final cameras = await availableCameras();
 // 最初のカメラデバイスを取得（一般的には、デバイスに複数のカメラがある場合、最初に使うカメラを選択）
  final firstCamera = cameras.first;
  runApp(MyApp(camera: firstCamera));
}
class MyApp extends StatelessWidget {
  const MyApp({
    Key? key,
    required this.camera,
  }) : super(key: key);
  final CameraDescription camera;
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'カメラ機能',
      theme: ThemeData(),
      home: TakePictureScreen(camera: camera),
    );
  }
}
class TakePictureScreen extends StatefulWidget {
  const TakePictureScreen({
    super.key,
    required this.camera,
  });
  final CameraDescription camera;
  @override
  TakePictureScreenState createState() => TakePictureScreenState();
}
class TakePictureScreenState extends State<TakePictureScreen> {
  late CameraController _controller;
  late Future<void> _initializeControllerFuture;
  @override
  void initState() {
    super.initState();
    _controller = CameraController(
       // 受け取ったカメラ情報を使用
      widget.camera,
      // 解像度を定義
      ResolutionPreset.medium,
    );
    // カメラの初期化処理を開始し、その完了を待つ
    _initializeControllerFuture = _controller.initialize();
  }
  @override
  void dispose() {
   // 画面が破棄される際にカメラコントローラーも破棄してリソースを解放
    _controller.dispose();
    super.dispose();
  }
  Future<void> _sendPhoto(File photo) async {
    var request = http.MultipartRequest(
      'POST',
      Uri.parse('http://IPアドレス(例:0.0.0.0:8000/process/request/'),);
    request.files.add(http.MultipartFile.fromBytes('photo', await photo.readAsBytes(), filename: 'photo.png',));
    try {
      var response = await request.send();
      if (response.statusCode == 200) {
        var contentBytes = await response.stream.toBytes();
        var base64Image = base64Encode(contentBytes);
        //表示用の画面に移動
        await Navigator.of(context).push(
          MaterialPageRoute(
            builder: (context) => DisplayProcessedPictureScreen(imagePath: 'data:image/png;base64,$base64Image'),
            fullscreenDialog : true,
          ),
        );
      } else {
        // 失敗時の処理
        print('デバッグのコード:レスポンスステータスコード = ${response.statusCode}');
      }
    }  catch (e) {
      // エラー時の処理
      print('デバッグのコード: エラーが発生しました: $e');
    }
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        // FutureBuilderで初期化を待ってからプレビューを表示 (それまではインジケータを表示)
        child: FutureBuilder<void>(
          future: _initializeControllerFuture,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.done) {
              return CameraPreview(_controller);
            } else {
              return const CircularProgressIndicator();
            }
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          // 写真を撮る
          final image = await _controller.takePicture();
          await _sendPhoto(File(image.path));
          // 表示用の画面に遷移
          await Navigator.of(context).push(
            MaterialPageRoute(
              builder: (context) => DisplayPictureScreen(imagePath: image.path),
              fullscreenDialog: false,
            ),
          );
        },
        child: const Icon(Icons.camera_alt),
      ),
    );
  }
}
// 撮影した写真を表示する画面
class DisplayPictureScreen extends StatelessWidget {
  const DisplayPictureScreen({super.key, required this.imagePath})  ;
  final String imagePath;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.blue,
        title: const Text(
          '通常',
          style: TextStyle(
            color: Colors.white, // 文字の色を白色に設定
            fontWeight: FontWeight.bold, // 文字を太字
          ),
        ),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.file(File(imagePath)),
            SizedBox(
                height: 20,
                width: 80),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                    onPressed: () {
                      // 「保存する」ボタンを押した時の処理
                      _saveImage(context, imagePath);
                    },
                    child: Text(
                      '保存する',
                      style: TextStyle(
                        color: Colors.white, //テキストを白字に設定
                        fontWeight: FontWeight.bold, //テキストを太字
                      ),
                    ),
                    style: ButtonStyle(
                      backgroundColor: MaterialStateProperty.all<Color>(Colors.blue),
                    )
                ),
                SizedBox(
                    height: 20,
                    width: 80),
                ElevatedButton(
                    onPressed: () {
                      // 「撮り直す」ボタンが押された時の処理
                      Navigator.of(context).pop();
                    },
                    child: Text(
                      '撮り直す',
                      style: TextStyle(
                        color: Colors.white, //テキストを白字に設定
                        fontWeight: FontWeight.bold, //テキストを太字
                      ),
                    ),
                    style: ButtonStyle(
                      backgroundColor: MaterialStateProperty.all<Color>(Colors.green),
                    )
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
  Future<void> _saveImage(BuildContext context, String imagePath) async {
    try {
      final result =await ImageGallerySaver.saveFile(imagePath);
      if (result != null) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('画像が保存されました')),
        );
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('画像の保存に失敗しました')),
        );
      }
    } catch (e) {
      print('画像の保存時にエラーが発生しました: $e');
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('画像の保存時にエラーが発生しました')),
      );
    }
  }
}
// 処理後の画像を受け取る
class DisplayProcessedPictureScreen extends StatefulWidget {
  final String imagePath;
  const DisplayProcessedPictureScreen({super.key, required this.imagePath});
  @override
  DisplayProcessedPictureScreenState createState() =>
      DisplayProcessedPictureScreenState();
}
class DisplayProcessedPictureScreenState extends State<DisplayProcessedPictureScreen> {
  Future<http.Response>? _fetchImageFuture;
  @override
  void initState() {
    super.initState();
    _fetchImage();
  }
  Future<void> _fetchImage() async {
    final base64Image = widget.imagePath
        .split(',')
        .last;
    final decodedBytes = base64Decode(base64Image);
    setState(() {
      _fetchImageFuture =
          Future.value(http.Response.bytes(decodedBytes, 200, headers: {
            HttpHeaders.contentTypeHeader: 'image/png',
          }));
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          backgroundColor: Colors.redAccent,
          title: const Text(
              'ノスタルジック',
              style: TextStyle(
                color: Colors.white, // 文字の色を白に設定
                fontWeight: FontWeight.bold, // 文字を太字
              ))),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            FutureBuilder<http.Response>(
              future: _fetchImageFuture,
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  final response = snapshot.data!;
                  if (response.statusCode == 200) {
                    final responseData = response.bodyBytes;
                    return Transform.rotate(
                      angle: 90 * pi /180,
                      child:// 反時計回りに90度回転
                      Image.memory(
                        responseData,
                        fit: BoxFit
                            .contain,
                      ),  // 画像をウィジェット内に適切に表示させるために適応させるfitパラメータを追加
                      alignment: Alignment.center, //回転の中心を設定
                    );
                  } else {
                    // レスポンスが不正なステイタスコードを返した場合のエラーメッセージ
                    return Text('画像の取得中にエラーが発生しました。');
                  }
                }
                return const CircularProgressIndicator();
              },
            ),
            SizedBox(height: 80),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () {
                    // 「保存する」ボタンを押した時の処理
                    _savedImage(context, widget.imagePath);
                  },
                  child: Text(
                    '保存する',
                    style: TextStyle(
                      color: Colors.white, // テキストを白字に設定
                      fontWeight: FontWeight.bold, // テキストを太字
                    ),
                  ),
                  style: ButtonStyle(
                    backgroundColor: MaterialStateProperty.all<Color>(Colors.blue), //ボタンの背景色を青色に設定
                  ),
                ),
              ],
            ),
            SizedBox(height: 10),
            ElevatedButton(
                onPressed: () {
                  // 「撮り直す」ボタンが押された時の処理
                  Navigator.of(context).pop();
                },
                child:Text(
                  '破棄',
                  style: TextStyle(
                    color: Colors.white, // テキストを白色に設定
                    fontWeight: FontWeight.bold, // テキストを太字
                  ),
                ),
                style: ButtonStyle(
                  backgroundColor: MaterialStateProperty.all<Color>(Colors.red), //ボタンの背景色を赤色に設定
                )
            ),
          ],
        ),
      ),
    );
  }
  Future<void> _savedImage(BuildContext context, String imagePath) async {
    try {
      final base64Image = widget.imagePath
          .split(',')
          .last;
      final decodedBytes = base64Decode(base64Image);
      final rotatedBytes = await _rotatedImage(decodedBytes, 90);
      // 画像を保存
      final result = await ImageGallerySaver.saveImage(rotatedBytes);
      if (result != null) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('画像が保存されました')),
        );
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('画像の保存に失敗しました')),
        );
      }
    } catch (e) {
      print('画像の保存時にエラーが発生しました: $e');
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('画像の保存時にエラーが発生しました')),
      );
    }
  }
  Future<Uint8List> _rotatedImage(Uint8List imageBytes, double angle) async {
    // 画像をデコード
    final image = await decodeImageFromList(imageBytes);
    // 回転後のサイズを計算
    final double radians = angle * ( pi / 180);
    final double sine = sin(radians);
    final double cosin = cos(radians);
    final double newWidth = (image.width * cosin + image.height * sine).abs();
    final double newHeight = (image.width * sine + image.height * cosin).abs();
    // 新しいサイズのキャンバスを作成
    final recorder = PictureRecorder();
    final canvas = Canvas(recorder, Rect.fromPoints(Offset(0.0,0.0), Offset(newWidth, newHeight)));
    // 画像を描画して回転
    canvas.translate(newWidth / 2, newHeight / 2);
    canvas.rotate(radians);
    canvas.drawImage(image, Offset(-image.width / 2, -image.height / 2), Paint());
    // キャンバスを終了し、画像を取得
    final picture = recorder.endRecording();
    final img = await picture.toImage(newWidth.toInt(), newHeight.toInt());
    final byteData = await img.toByteData(format: ImageByteFormat.png);
    return byteData!.buffer.asUint8List();
  }
}
```

最終的なinfo.plistは、
```diff_plist: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>CADisableMinimumFrameDurationOnPhone</key>
	<true/>
	<key>CFBundleDevelopmentRegion</key>
	<string>$(DEVELOPMENT_LANGUAGE)</string>
	<key>CFBundleDisplayName</key>
	<string>Take Photo</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>photo</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>NSCameraUsageDescription</key>
	<string>撮影をするために、カメラ機能を使う必要があります。</string>
	<key>NSMicrophoneUsageDescription</key>
	<string>撮影をする際に、マイク機能を使う場合があります。</string>
+ <key>NSPhotoLibraryAddUsageDescription</key>
+ <string>撮影した写真を保存します</string>
	<key>UIApplicationSupportsIndirectInputEvents</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>
</dict>
</plist>
```
となり、

pubspec.yamlは、
```yaml:pubspec.yaml


name: photo
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
environment:
  sdk: '>=3.3.4 <4.0.0'
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter
  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.6
  camera: ^0.11.0+2
  path_provider: ^2.1.5
  path: ^1.9.0
  video_player: ^2.9.2
  image_gallery_saver: ^2.0.3
  http: ^1.2.2
dev_dependencies:
  flutter_test:
    sdk: flutter
  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  flutter_lints: ^3.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true
  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg
  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware
  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages
  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages
```
というコードになっていれば、OKです。

では、この作成したphotoプロジェクトを

先ほど、作成したCameraディレクトリ内にコピーしていきます。

なので、photoプロジェクトのパスをAndroid Studio内のターミナルで、
```
$ pwd
```
と打ち、取得します。

<font color=red>※ 表示されたメッセージがphotoプロジェクトのパスになります。</font>

そして、次に、Cameraディレクトリ内で、同じく、
```
$ pwd
```
と打ち、
```
$ cp -r [photoプロジェクトのパス]  [Cameraディレクトリのパス/photo]
```
とコマンドで入力してください。

このようなディレクトリ構造になっていれば大丈夫です。
```
Camera
├── photo
└── python
     └── process
```
より詳しい全体的なディレクトリ構造は、
```
Camera
├── python
│     ├── Dockerfile
│     ├── manage.py
│     ├── process
│     │   ├── __init__.py
│     │   ├── admin.py
│     │   ├── apps.py
│     ├── migrations
│     │   │   └── __init__.py
│     │   ├── models.py
│     │   ├── tests.py
│     │   ├── urls.py
│     │   └── views.py
│     └── python
│           ├── __init__.py
│           ├── __pycache__
│           │   ├── __init__.cpython-311.pyc
│           │   └── settings.cpython-311.pyc
│           ├── asgi.py
│           ├── settings.py
│           ├── urls.py
│           └── wsgi.py
└── photo   # Flutterアプリケーション
      
```
となっていれば大丈夫です。

では、photoディレクトリ内で、
```
$ flutter run
```
を実行してみましょう。

もし、
![AD_4nXfz_0yHJ-pCj52kIf3UfTZsls8d4q3bvmnDYmnow46Tr96yXF6njJT1xW5Of0ixevnR2-sYKTKs-TOYdp4w1fgtkhMc8FojgpvEh5hpMjj9WhkszEEFJZkT.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/e171f5ba-21bc-48d1-79ed-096b770da30a.jpeg)


このような画面が出てきてしまったら、

下の画面のように、XCodeのSettings項目を押し、
![About Xcode.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/0aed4e71-5280-ed10-74cf-5b913475ebc3.jpeg)

Components内にある
エラーメッセージで表示している必要としているデバイス(僕の場合は、iOS 18.2)を選び、「GET」ボタンを押してください。

![AD_4nXeMI8zSRxCMDxOxDIa1kles8II9cx2-Xg9qdJ-Xpl4PCU1bm1Rm5uK4vnPX9GQyLwKdSxZjuOVwApmM2SRtPkNuB9Rv_wpoX0KyYj6x4YIVotH6pE46C3An.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/33ac2539-bb0c-b83b-c2a9-4d6c6cce935d.jpeg)


これをすることで、エラーが解消されるかと思います。

では、これまでに書いたコードが動くかどうかテストをしていきます。


FlutterとDjangoは、手動で動かしていきます。

撮影した写真を加工するために、まずは、Djangoのサーバーを動かしていきます。

Djangoを動かす前に、

サーバーのCORS設定を確認（特にローカル開発環境）
モバイルデバイスからのリクエストをサーバーが受け入れるためには、サーバーのCORS（Cross-Origin Resource Sharing）設定が適切に設定されている必要があります。

特に、FlutterアプリがAndroidやiOS端末で実行されている場合、サーバー側でCORSが適切に設定されていないと、リクエストが拒否されることがあります。

例えば、DjangoでCORSを許可するには、django-cors-headersをインストールして設定します。
```
$ pip install django-cors-headers
```

次に、settings.pyで設定します。

```py:python/python/settings.py
INSTALLED_APPS = [
    ...
    'corsheaders',
    ...
]

MIDDLEWARE = [
    ...
    'corsheaders.middleware.CorsMiddleware',
    ...
]

# 必要に応じて設定（すべてのドメインからのリクエストを許可する場合）
CORS_ALLOW_ALL_ORIGINS = True
```

もし、下のようなエラーが出た場合は、
![AD_4nXeSaQ0FxHBeeqBbkSI_rhtDLxfxaBjAkRUiygMbK_yo2o2cats6GlimbzbCvBTSkNpx6t-0D3gtnxv5vNOUHey9BqD6SPjeUp5Tx5vgEfpgz0JbfnAA_fZl.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/d48d13d5-a5ab-4506-e4f7-0fabe2002270.jpeg)


Pythonのsslモジュールが利用できないことが原因です。sslモジュールは、SSL/TLS接続を扱うために必要ですが、現在のPython環境ではこれが有効になっていない可能性があります。

1. Pythonのsslモジュールのインストールを確認

この問題は、Pythonをインストールする際にsslサポートが無効化された場合に発生します。これを解決するために、次の手順を試してみてください。

2. pyenvの再インストール
PythonのSSLサポートを有効にするために、pyenvでPythonを再インストールする際に、opensslを適切にリンクさせてインストールします。次のコマンドを試してください：
```
$ env PATH="$(brew --prefix openssl@3)/bin:$PATH" pyenv install 3.11.4
```
openssl@3を正しくリンクさせてPythonをインストールします。これにより、PythonがSSLモジュールを適切に利用できるようになります。

3. pyenvのPythonバージョンを再設定

インストールが完了したら、pyenvでインストールしたPythonバージョンを使用するように設定します。
```
$ pyenv global 3.11.4
```
4. Pythonの依存関係の再インストール

SSLモジュールが有効になったら、必要なパッケージを再インストールします。
```
$ pip install --upgrade pip
$ pip install django-cors-headers
```
これで、pipが正しくHTTPS経由でパッケージをダウンロードできるようになるはずです。


これらを踏まえて、python/python/settings.pyの全体的な内容は、
```diff_python:python/python/settings.py
"""
Django settings for python project.
Generated by 'django-admin startproject' using Django 4.2.2.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""
from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-uvw0i=(例:########################)'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = [IPアドレス(例:'0.0.0.0')]
# Application definition
INSTALLED_APPS = [
+ 'corsheaders',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
+ 'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'python.urls'
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
WSGI_APPLICATION = 'python.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+ # 必要に応じて設定(全てのドメインからのリクエストを許可する場合)
+ CORS_ALLOW_ALL_ORIGINS = True
```
に変わります。

では、Cameraディレクトリ内のpython(manage.pyがある)ディレクトリ内で、
```
$ python manage.py runserver IPアドレス(例:0.0.0.0:8000)
```
というコマンドを打ってください。
![AD_4nXdncdKCfLgOUElkjg7ZRRj9h7NzYyV9anDICcgEwO8wCs25fHKPx7aoKad5Aj6ZpU7FBOEI6dPtXAPYnI3LZhCNhYaNYRj2XgXq46a9Y484o9jS4bFBYgDO.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/e76024ff-fbf1-b226-4e3e-b0e3ba7d746a.jpeg)

上のようなメッセージが、出れば成功です。

それでは、スマホとパソコンをコードで同期してください。

次は、フロントエンドであるFlutterを動かしていきたいので、photoディレクトリ内で、
```
$ flutter run
```

とコマンドを打ってください。

立ち上がるまでに、時間がかかりますが、
![DevTools.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/bc93aa4e-5d31-7199-93a7-4e521be499b8.jpeg)



上のようなメッセージが出れば成功です。

すると、同期したスマホの画面が切り替わり、

![AD_4nXdPGD2cS46u4K8PFPlrGKdaFhILQLOwXilTiK_MkWs7n2ipo5Gt3GdyHD89_w93O3pxCii0CN5jo-2pswR-w8HmZkkQy9L2NZspb4yieCHL3J-byuawBRqJ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/77da0473-674a-18cb-3417-70381e843c11.jpeg)

という画面に変わります。

そして、また、カメラボタンを押すと、

![ノスタルジック.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/1c3e0cad-f129-a1cc-59f0-c646ac544eea.jpeg)


今度は、上のような画面が現れるかと思います。

この画像に変われば、Djangoとの通信ができています。

そして、「保存する」ボタンを押すと、
![ノスタルジック.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/90bf9054-27ca-5af1-3bc9-f68b2e23df19.jpeg)



というメッセージが出ます。

そして、写真アルバムを見てみると、

![AD_4nXftq_43nPIIV2yYlogYYjeD3xX56Vr-XYIxKBpqbq7CiUs07fmB7srLiBqwiYDkAglIpmjxHdTNC8sONP1wtN-8tbmvvadBSi6Mp-DnbTxG9pnMwYj6aVWW.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/cabe6c7f-06ba-e056-444f-f676e92397cc.jpeg)


ちゃんと保存されていますね。

次に、「破棄」ボタンを押すと、

![AD_4nXe6EpBM-tfBGfNXCJyc8G9jpBjBqEmdQqGXq3-NAejdXxEe4ulEJ_aQOmciNBycoBk8B2k7Bnq6BMXZxqB0-la3QDAx64KEmby2q5xynQj3kHo5fxcsuVT5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/1553ece1-c30f-b809-fbda-c1427efdb1ba.jpeg)

加工をしていない画面に遷移します。

こちらも、「保存する」ボタンを押すと、

![AD_4nXf6NE6XXsSLF8ig8eRtXNnUiAAGUky_KGShDrMAMmzITmkPrSVsAGyrUN02w5DxYNL9DbpLQWnD1XOnrh1qTjJZfAF2MIzK8qkD5NfGrVm6xaZwfLFRBN5R.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/92fd4141-d278-079a-2f0c-5760e7dd6e59.jpeg)


というメッセージが出ます。

また、写真アルバムを確認すると、

![AD_4nXeDO7jsjKfV6TpIRQl_DgYdEKcYue--zkGKHW0mpXG58L_Dxy6ww2Ma6qU51WH17hxqNNKxHTz9QV5RiWOTTR-uo0Cgd78YyIY9XNnBdtRJ2JYxXH6iJt2L.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/035f1b6a-a9ab-0da5-1bf9-8721a124b8fa.jpeg)


こちらもちゃんと保存されています。

そして、「撮り直す」ボタンを押すと、

![AD_4nXdPGD2cS46u4K8PFPlrGKdaFhILQLOwXilTiK_MkWs7n2ipo5Gt3GdyHD89_w93O3pxCii0CN5jo-2pswR-w8HmZkkQy9L2NZspb4yieCHL3J-byuawBRqJ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945016/961d28cd-39fa-eb48-5c25-50ef2763ae3f.jpeg)


また、撮影画面に戻ります。

ここまでできれば、成功です。

これで完成です。

ここまでの工程は、Githubに上げておきます↓

https://github.com/makkyo-git/camera

もし、加工する写真をもっと赤味を強くしたり、青みがかったりしたい場合は、

**python/process/views.py**の

**<font color=red>new_r</font>** や **<font color=green>new_g</font>**、<font color=blue>**new_b**</font>の数値を変えて、お好みの数値に設定してみてください。

今回は、Dartというプログラミング言語でFlutterというフレームワークとPythonというプログラミング言語でDjangoというフレームワークを使って、

一度は、スマホアプリを作ってみたいと思い、簡単な写真加工アプリを作ってみて、出来たので、公開しました。

次は、

業務で使っていたTypeScriptという言語でReactというフレームワークとPHPという言語を活かして、

LEMP(Linux、Nginx、MySQL、PHP)という環境を構築して、

簡単な掲示板アプリを作ってみます。↓

https://qiita.com/makkyo-git/items/4d219c016b4c488fdae1