Canvasの画像をCloud Storageに保存して動的なOGP画像として設定する

# 完成したサービス

https://twitter.com/motsu_u/status/1641032741591416834

## 概要
ユーザーはブラウザ上で麻雀の手牌を構築する。
作成された手牌を登録するとURLが発行される。
発行されたURLをTwitter等で共有すると、作成した手牌の画像がOGP画像として表示される。

# 処理

## 大まかな図
![map.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205287/5b8c7c51-bdd4-abcf-139d-1f6cc84e8cf4.jpeg)

## 処理の流れ
ユーザーの操作によりCanvas描画。
　→Canvasをphpファイルに送信し、JPEG化。
　→JPEGをCloud Storageに保存。
　→一意なIDを発行し、JPEGのファイル名と共にDBに保存。
　→IDをクエリパラメータとして持つアクセスに対してCloud Storageの画像をOGP画像として返却。

# コード
## Canvasをphpへ送信
```JavaScript:canvas.js
const canvas = document.getElementById('canvas');
// 適当な描画処理
const dataURL = canvas.toDataURL();
fetch('save.php',{
  method: 'POST',
  body: JSON.stringify({
    canvas: dataURL,
  })
}).then(response=>{
  if(!response.ok) throw new Error();
  return response.text();
}).then(text=>{
  //成功時の処理
}).catch(error=>{
  //失敗時の処理
});
```
## CanvasをJPEG化
```php:save.php
$input = file_get_contents('php://input');
$json = json_decode($input, true);
$data_canvas = $json['canvas'];
$str_canvas64 = preg_replace('/^data:image\/png;base64,/i', '', $data_canvas);
$str_canvas = base64_decode($str_canvas64);
$canvas = imagecreatefromstring($str_canvas);
$image = image_data($canvas);

function image_data($gd){
  ob_start();
  imagejpeg($gd,null,75);
  return(ob_get_clean());
}
```

## JPEG化した画像ファイルをCloud Storageへアップロード
```php:save.php
require __DIR__.'/vendor/autoload.php';
use Google\Cloud\Storage\StorageClient;

const key_path = 'path/to/keyFile.json';
const project_id = 'PROJECT_ID';
const bucket_name = 'BUCKET_NAME';
const secretKEY = 'SECRET_KEY';

$input = file_get_contents('php://input');
$json = json_decode($input, true);
$data_canvas = $json['canvas'];
$str_canvas64 = preg_replace('/^data:image\/png;base64,/i', '', $data_canvas);
$str_canvas = base64_decode($str_canvas64);
$canvas = imagecreatefromstring($str_canvas);
$image = image_data($canvas);
$filename = 'FILE_NAME.jpg';

try{
  $storage = new StorageClient([
    'keyFilePath' => key_path
  ]);
  $bucket = $storage->bucket(bucket_name);
  $bucket->upload($image, [
    'name' => $filename
  ]);
}catch (PDOException $e) {
  
}

function image_data($gd){
  ob_start();
  imagejpeg($gd,null,75);
  return(ob_get_clean());
}
```

## Cloud Storage内のファイルへアクセス
```php:index.php
<?php
  $filename = 'FILE_NAME.jpg';
  $src_img = 'https://storage.googleapis.com/BUCKET_NAME/'.$filename;
?>

<head>
  <!-- ~~ -->
  <meta property="og:image" content="<?php echo $src_img ?>">
  <!-- ~~ -->
</head>
<!-- ~~ -->
```

# 余談
当初は全てfirebaseで完結させようと考えていたが、Cloud Functionsの設定がどうにも上手く行かずに頓挫。
ファイル保管の部分だけCloud Storageに任せ、その他はレンタルサーバーで賄うこととなった。

# 追記(JavaScriptだけでアップロードする方法)
canvasからそのままCloud Storageへアップロードすることが可能だった。
```:省略
npm install ~
```
```javascript:foo.js
import firebase from 'firebase/compat/app';
import { getStorage, uploadBytes, ref } from "firebase/storage";

const firebaseConfig = {
  //apikey等色々
};
firebase.initializeApp(firebaseConfig);

saveCanvas();

async function saveCanvas(){
  const canvas = document.createElement('canvas');
  // 描画処理なんやかんや
  try{
    const blob = await new Promise(resolve=>canvas.toBlob(resolve,'image/webp',.75);
    const storage = getStorage();
    const storageRef = ref(storage, 'fileName.webp');
    await uploadBytes(storageRef, blob);
  }catch{
    //エラー時処理
  }
}
```