From 734c41aba5b3a7e41a1d65796f34d68da77248f8 Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Sun, 2 Jul 2023 13:46:49 +0900
Subject: [PATCH] =?UTF-8?q?perf(frontend):=20MkImgWithBlurhash=E3=81=A7blu?=
 =?UTF-8?q?rhash=E6=8F=8F=E7=94=BB=E3=81=AB=E4=BD=BF=E3=81=86canvas?=
 =?UTF-8?q?=E3=81=AF=E5=86=8D=E5=88=A9=E7=94=A8=E3=81=99=E3=82=8B=E3=82=88?=
 =?UTF-8?q?=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B=20(#10966)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* blurhashを描画するためのcanvasは再利用する

* Revert "perf(frontend): WebGL contextの数を減らす"

This reverts commit aeb8955ca2600e801d44dcac2005fc994e665a6c.

* MkAvatarは平均色だけにする

* clean up

* fix
---
 .../src/components/MkImgWithBlurhash.vue      | 51 ++++++++++++-------
 .../src/components/global/MkAvatar.vue        |  3 +-
 .../frontend/src/workers/draw-blurhash.ts     |  8 +--
 3 files changed, 39 insertions(+), 23 deletions(-)

diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue
index 672a28f6d0..cb229fa241 100644
--- a/packages/frontend/src/components/MkImgWithBlurhash.vue
+++ b/packages/frontend/src/components/MkImgWithBlurhash.vue
@@ -22,10 +22,13 @@ import TestWebGL2 from '@/workers/test-webgl2?worker';
 import { WorkerMultiDispatch } from '@/scripts/worker-multi-dispatch';
 import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash';
 
-const workerPromise = new Promise<WorkerMultiDispatch | null>(resolve => {
+const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resolve => {
 	// テスト環境で Web Worker インスタンスは作成できない
 	if (import.meta.env.MODE === 'test') {
-		resolve(null);
+		const canvas = document.createElement('canvas');
+		canvas.width = 64;
+		canvas.height = 64;
+		resolve(canvas);
 		return;
 	}
 	const testWorker = new TestWebGL2();
@@ -38,7 +41,10 @@ const workerPromise = new Promise<WorkerMultiDispatch | null>(resolve => {
 			resolve(workers);
 			if (_DEV_) console.log('WebGL2 in worker is supported!');
 		} else {
-			resolve(null);
+			const canvas = document.createElement('canvas');
+			canvas.width = 64;
+			canvas.height = 64;
+			resolve(canvas);
 			if (_DEV_) console.log('WebGL2 in worker is not supported...');
 		}
 		testWorker.terminate();
@@ -70,6 +76,7 @@ const props = withDefaults(defineProps<{
 	width?: number;
 	cover?: boolean;
 	forceBlurhash?: boolean;
+	onlyAvgColor?: boolean; // 軽量化のためにBlurhashを使わずに平均色だけを描画
 }>(), {
 	transition: null,
 	src: null,
@@ -79,6 +86,7 @@ const props = withDefaults(defineProps<{
 	width: 64,
 	cover: true,
 	forceBlurhash: false,
+	onlyAvgColor: false,
 });
 
 const viewId = uuid();
@@ -139,8 +147,8 @@ function drawImage(bitmap: CanvasImageSource) {
 	ctx.drawImage(bitmap, 0, 0, canvasWidth, canvasHeight);
 }
 
-async function draw() {
-	if (!canvas.value || props.hash == null) return;
+function drawAvg() {
+	if (!canvas.value || !props.hash) return;
 
 	const ctx = canvas.value.getContext('2d');
 	if (!ctx) return;
@@ -149,25 +157,28 @@ async function draw() {
 	ctx.beginPath();
 	ctx.fillStyle = extractAvgColorFromBlurhash(props.hash) ?? '#888';
 	ctx.fillRect(0, 0, canvasWidth, canvasHeight);
+}
+
+async function draw() {
+	if (props.hash == null) return;
 
-	const workers = await workerPromise;
-	if (workers) {
-		workers.postMessage(
+	drawAvg();
+
+	if (props.onlyAvgColor) return;
+
+	const work = await canvasPromise;
+	if (work instanceof WorkerMultiDispatch) {
+		work.postMessage(
 			{
 				id: viewId,
 				hash: props.hash,
-				width: canvasWidth,
-				height: canvasHeight,
 			},
 			undefined,
 		);
 	} else {
 		try {
-			const work = document.createElement('canvas');
-			work.width = canvasWidth;
-			work.height = canvasHeight;
 			render(props.hash, work);
-			ctx.drawImage(work, 0, 0, canvasWidth, canvasHeight);
+			drawImage(work);
 		} catch (error) {
 			console.error('Error occured during drawing blurhash', error);
 		}
@@ -179,9 +190,9 @@ function workerOnMessage(event: MessageEvent) {
 	drawImage(event.data.bitmap as ImageBitmap);
 }
 
-workerPromise.then(worker => {
-	if (worker) {
-		worker.addListener(workerOnMessage);
+canvasPromise.then(work => {
+	if (work instanceof WorkerMultiDispatch) {
+		work.addListener(workerOnMessage);
 	}
 
 	draw();
@@ -204,8 +215,10 @@ onMounted(() => {
 });
 
 onUnmounted(() => {
-	workerPromise.then(worker => {
-		worker?.removeListener(workerOnMessage);
+	canvasPromise.then(work => {
+		if (work instanceof WorkerMultiDispatch) {
+			work.removeListener(workerOnMessage);
+		}
 	});
 });
 </script>
diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue
index efe74b7cc3..1952ba9811 100644
--- a/packages/frontend/src/components/global/MkAvatar.vue
+++ b/packages/frontend/src/components/global/MkAvatar.vue
@@ -1,6 +1,6 @@
 <template>
 <component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.animation]: animation, [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick">
-	<img :class="$style.inner" :src="url" :hash="user?.avatarBlurhash" :cover="true"/>
+	<MkImgWithBlurhash :class="$style.inner" :src="url" :hash="user?.avatarBlurhash" :cover="true" :onlyAvgColor="true"/>
 	<MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/>
 	<div v-if="user.isCat" :class="[$style.ears]">
 		<div :class="$style.earLeft">
@@ -24,6 +24,7 @@
 <script lang="ts" setup>
 import { watch } from 'vue';
 import * as misskey from 'misskey-js';
+import MkImgWithBlurhash from '../MkImgWithBlurhash.vue';
 import MkA from './MkA.vue';
 import { getStaticImageUrl } from '@/scripts/media-proxy';
 import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash';
diff --git a/packages/frontend/src/workers/draw-blurhash.ts b/packages/frontend/src/workers/draw-blurhash.ts
index 5f2168a44a..e0672d5424 100644
--- a/packages/frontend/src/workers/draw-blurhash.ts
+++ b/packages/frontend/src/workers/draw-blurhash.ts
@@ -1,5 +1,7 @@
 import { render } from 'buraha';
 
+const canvas = new OffscreenCanvas(64, 64);
+
 onmessage = (event) => {
     // console.log(event.data);
     if (!('id' in event.data && typeof event.data.id === 'string')) {
@@ -8,8 +10,8 @@ onmessage = (event) => {
     if (!('hash' in event.data && typeof event.data.hash === 'string')) {
         return;
     }
-    const work = new OffscreenCanvas(event.data.width ?? 64, event.data.height ?? 64);
-    render(event.data.hash, work);
-    const bitmap = work.transferToImageBitmap();
+
+    render(event.data.hash, canvas);
+    const bitmap = canvas.transferToImageBitmap();
     postMessage({ id: event.data.id, bitmap });
 };
-- 
GitLab