【Laravel】画像ファイルのEXIF情報をImagickで削除する

画像投稿機能を実装したシステムを構築する際に、注意する点として挙げられるのがEXIF情報です。
EXIF情報には画像に関する情報がメタデータとして含まれています。
その情報の中には緯度経度が含まれていることがあります。
これらの情報を適切に操作しなければ、ユーザーをストーカー被害に遭わせてしまう危険性があります。
そのEXIF情報をPHPの拡張モジュールであるImagickを使用して削除します。
またImagickのその他の使用方法についても解説します。

# インストール方法

### ImageMagickのインストール
```shell:shell
    sudo apt-get install -y imagemagick
```
### Imagickのインストール
```shell:shell
    sudo pecl install imagick
```

### php.iniの編集
```shell:shell
    # php.iniのパスを表示
    php --ini

    # viエディタでphp.iniを編集
    sudo vi /path/path/path/path/php.ini
```
```ini:php.ini
    # 省略
    
    # 以下の文を追加
    extension=imagick.so
```
これでインストールは終了です。
Webサーバーの再起動を行えば準備完了です。

# 使用方法
### Imagickインスタンスに画像ファイルの引き渡し
Imagickの```readImage()```メソッドでは```UploadedFile```型の引数を受け取ります。
つまりは、Bladeから送られてきた画像ファイルを```$request->file()```メソッドで取り出し、そのままインスタンスにセットすることが可能です。
```html:example.blade.php
    <form action="{{ route('example.store') }}" method="POST" enctype="multipart/form-data">
        <input type="file" name="image" accept="image/*">
    </form>
```
上記のようなBladeで画像ファイルをPOSTする際、バックエンド側では以下のようなコードでImagickインスタンスに画像ファイルをセットできます。
```php:app/Http/Controllers/ExampleController.php
    use Imagick;

    public function store(Request $request)
    {
        $imagick = new Imagick();
        $imagick->readImage($request->file('image'));
        
    }
```
:::note warn
画像ファイルを受け取る場合、適切なバリデーションを行なってください。
データ型、MIMEタイプ、最大容量等を限定し、悪意のあるリクエストへの対策をしましょう。
:::

### EXIF情報の存在確認および削除
```getImageProperties('exif:*')```ではその画像ファイルのEXIF情報を取得しています。
```stripImage()```では画像ファイルのEXIF情報を削除します。
基本的に多くのサービスではEXIFが適切に削除されており、EXIF情報が存在していない画像もある為、その場合はスルーします。
```php:app/Http/Controllers/ExampleController.php
    # 先ほどの処理に続く
    
    if (!is_null($imagick->getImageProperties('exif:*'))) {
        $imagick->stripImage();
    }

```
この処理で位置情報を削除でき、安全に画像サービスを構築できます。
これ以下は別の用途での使用方法です。

### MIMEタイプ、 画像フォーマットの取得
元画像をアーカイブとしてストレージに保存し、DBに情報を残す場合など有効活用できそうです。
```php:app/Http/Controllers/ExampleController.php
    $imagick->getImageMimetype(); #JPEGの場合、戻り値は'image/jpeg'
    $imagick->getImageFormat(); #JPEGの場合、戻り値は'JPEG'
```



### 画像フォーマットの変換
JPEGの1/5ほどの容量に圧縮されるものの画質低下が起こらないWebPという画像フォーマットに変換します。
WebPにすることで容量が軽くなり、画像表示の際の読み込みが高速化します。SEO対策にも良いみたいです。
```php:app/Http/Controllers/ExampleController.php
    $imagick->setImageFormat('webp');
```
引数には各画像フォーマット名を入れることで別のフォーマットにも変換できます。

### バイナリーデータ化
```getImagesBlob()```メソッドでは画像ファイルをバイナリーデータ化できます。
```php:app/Http/Controllers/ExampleController.php
    $imagick->getImagesBlob();
```
「あ〜DBにBLOB型で画像入れちゃう子ね」ってなったかもしれませんが、用途は別です。
バイナリーデータ化する事で、S3などのストレージにアップロードが可能になります。
```php:app/Http/Controllers/ExampleController.php
    $path = 'image/fruits/apple.webp';
    Storage::disk('s3')->put($path, $imagick->getImagesBlob());
```
この処理でEXIF削除後の画像ファイルをストレージに保存できるようになります。
S3であれば```image/fruits```prefixにapple.webpという名前で画像が保存されます。

### Imagickインスタンスにバイナリーデータの引き渡し
```php:app/Http/Controllers/ExampleController.php
    $imagick = new Imagick();
    $imagick->readImageBlob($blob); # $blobはバイナリデータの文字列です
```
この処理とすでに紹介した処理を合わせることで、DBに保存されているBLOB型のデータを画像ファイルとして復元し、EXIF削除やフォーマット変換を含めたストレージへの移行バッチ処理などに応用できます。

### リサイズ
##### ```resizeImage()```
```php:app/Http/Controllers/ExampleController.php
    $width = 200; # px単位
    $height = 200; # px単位
    $imagick->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1);
```
第3引数はリサイズ時のフィルターになり```Imagick::FILTER_LANCZOS```を使用することで高品質のままリサイズが可能になります。
第4引数ではBlurを指定し、1より大きいとぼかしが強くなり、1より小さいとシャープな画像になります。
##### ```thumbnailImage()```
サムネイルなどを作成する場合は```thumbnailImage()```を使用します。```resizeImage()```は```thumbnailImage()```に比べメモリ使用量が多いようです。
またメタデータもついでに削除し、最適化されたリサイズを行なってくれます。
```php:app/Http/Controllers/ExampleController.php
    $width = 200; # px単位
    $height = 200; # px単位
    $imagick->thumbnailImage($width, $height, true);
```


### 参考：Imagickドキュメント
https://www.php.net/manual/ja/class.imagick.php

# 最後に
ImageMagickは画像処理ツールとして最も広く使用されているもの1つである一方、過去にImageTragick等の深刻な脆弱性が発見されたこともあります。
強力なツールではあるものの、セキュリティ対策を怠ることはリスクに繋がる為、最新バージョンへのアップデートや使用用途を絞ったり、代替ソフトで対応するなどといったことも必要になる場合があります。