1
1

画像をリサイズ&クロップしてFirebase CloudStorageにアップロードする

Last updated at Posted at 2023-11-23

やりたいこと

Vue.jsで下記の動作を実現する:

  1. formから画像をアップロード
  2. アップロードした画像のアスペクト比を保ったまま正方形にリサイズ & 円形にクロップ
  3. 加工した画像をFirebase ColudStrageにアップロード

画像のリサイズはなんとなくサーバーサイドでやるもの?と思っていたのですが、これくらいの操作であれば、canvasを使えばいけました。

formから画像をアップロード

Templete内のform部分はこんな感じ。

upload.vue
<form @submit.prevent="submitImg" class="form">
			<label class="input_label" :class="{ disabled: uploading }">
				<canvas id="preview" class="preview" ref="canvas"></canvas>
				<div class="uploading" v-if="uploading">
					<img src="../assets/bars.svg" class="loading" />
				</div>
				<input
					class="input_file"
					type="file"
					accept="image/*"
					@change="changeImg"
				/>
			</label>
			<button v-if="isAttached" type="submit" class="button">
				<span v-if="!uploading">これにする</span>
				<span v-else>アップロード中...</span>
			</button>
		</form>

dataには、下記データを用意します。

upload.vue
data() {
			return {
				thumbnail: new Blob(), //画像を格納する
				isAttached: false, //画像が添付されたかどうか
				uploading: false, //アップロード中かどうか(ローディング表示につかう)
				getData: {
					thumbnail: "", //firebaseからのレスポンスを格納する
				},
			};
		},

画像プレビューをcanvasにしているのがポイントです。
CSSは割愛しますが、UIはこんなです。
円をタップすると画像ファイルを選択できる。
スクリーンショット 2023-11-23 8.39.12.png

formで選択した画像を正方形にクロップ & リサイズ

上記で画像を選択するとchangeImgが発火します。
下記をmethodsに追加します。
画像をアスペクト比を保ったまま512x512の正方形にリサイズし、円にクロップします。

upload.vue
changeImg(e: any) {
				this.thumbnail = e.target.files[0];
				const image = new Image();
				if (this.thumbnail) {
					this.isAttached = true;
					const reader = new FileReader();
					reader.readAsDataURL(this.thumbnail);
					reader.onload = () => {
						image.src = URL.createObjectURL(this.thumbnail);
						const canvas = this.$refs["canvas"] as any;
						const ctx = canvas.getContext("2d");
      
						canvas.width = 512;
						canvas.height = 512;

						image.addEventListener("load", () => {
							const imageWidth = image.naturalWidth;
							const imageHeight = image.naturalHeight;

							const iconSize = canvas.width;

                            //画像をアスペクト比を保ち正方形にリサイズ
							const aspectRatio = imageWidth / imageHeight;
							let imageViewWidth, imageViewHeight, offsetX, offsetY;

							if (aspectRatio >= 1) {
								imageViewWidth = iconSize * aspectRatio;
								imageViewHeight = iconSize;
								offsetX = (iconSize - imageViewWidth) / 2;
								offsetY = 0;
							} else {
								imageViewWidth = iconSize;
								imageViewHeight = iconSize / aspectRatio;
								offsetX = 0;
								offsetY = (iconSize - imageViewHeight) / 2;
							}
							ctx.clearRect(0, 0, canvas.width, canvas.height);
       
                           //円にくりぬく
							ctx.beginPath();
							ctx.arc(iconSize / 2, iconSize / 2, iconSize / 2, 0, Math.PI * 2);
							ctx.closePath();
							ctx.clip();

                            // 描画する
							ctx.drawImage(
								image,
								0,
								0,
								imageWidth,
								imageHeight,
								offsetX,
								offsetY,
								imageViewWidth,
								imageViewHeight
							);

							ctx.clip("evenodd");

                            //アップロードする画像にcanvasで描画している内容を格納
							this.thumbnail = canvas.toDataURL();
						});
					};
				}
			},

Firebase CloudStorageの準備

ライブラリのインストールやConfig、アップロードはこの記事を参考にしました。

canvasで加工した画像をFirebase CloudStorageにアップロード

フォームをsubmitするとsubmitImgが発火します。
下記をmethodsに追加します。

upload.vue
submitImg() {
				let storage = getStorage();

				const canvas = this.$refs["canvas"] as any;

				const storageRefImagesRef = ref(
					storage,
                     //画像の格納先とファイル名を決めている
					"images/" + this.fileID + ".png"
				);
				const metadata = {
					contentType: "image/png",
				};
				canvas.toBlob((blob: any) => {
					const uploadTask = uploadBytesResumable(
						storageRefImagesRef,
						blob,
						metadata
					);

					uploadTask.on(
						"state_changed",
						(snapshot) => {
							const progress =
								(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
							console.log("Upload is " + progress + "% done");
							switch (snapshot.state) {
								case "paused":
									console.log("アップロード停止");
									break;
								case "running":
									this.uploading = true;
									console.log("アップロード中");
									break;
							}
						},
						(error) => {
							console.log("Upload error!");
							alert("ごめんなさい。アップロードエラーです!");
						},
						() => {
							getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
								this.uploading = false;
								this.$emit("uploaded", downloadURL);
								this.getData.thumbnail = downloadURL;
								console.log(downloadURL);
							});
						}
					);
				});
			},

getData.thumbnailにアップロードされたURLが返ってきます。

this.fileIDで画像名を決めていますが、今回は画像名をユニークIDにしたかったので、親コンポーネントでユニークIDを発行してそれをpropsとして渡しました。
そのあたりについては本筋とそれるので別記事にでも!

実行結果

こんな感じの縦長の画像をアップロードします。
pexels-valeria-boltneva-1805164.jpg

スクリーンショット 2023-11-23 9.10.29.png

正円にクロップされました。

download.png

横長でももちろん。
pexels-inge-wallumrød-126407.jpg
スクリーンショット 2023-11-23 9.12.22.png
download-1.png

さらにアプデしたいこと

今回は最低限でいいやと思い、トリミング範囲については自動的に中央基準になるように実装しています。
今後はトリミング範囲も任意に設定できるようにしたいですね。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1