perf(frontend): MkImgWithBlurhashでblurhash描画に使うcanvasは再利用するようにする (#10966)

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

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

This reverts commit aeb8955ca2.

* MkAvatarは平均色だけにする

* clean up

* fix
This commit is contained in:
tamaina 2023-07-02 13:46:49 +09:00 committed by GitHub
parent a1327fa9e1
commit 734c41aba5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 39 additions and 23 deletions

View file

@ -22,10 +22,13 @@ import TestWebGL2 from '@/workers/test-webgl2?worker';
import { WorkerMultiDispatch } from '@/scripts/worker-multi-dispatch'; import { WorkerMultiDispatch } from '@/scripts/worker-multi-dispatch';
import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash'; 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 // Web Worker
if (import.meta.env.MODE === 'test') { if (import.meta.env.MODE === 'test') {
resolve(null); const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
resolve(canvas);
return; return;
} }
const testWorker = new TestWebGL2(); const testWorker = new TestWebGL2();
@ -38,7 +41,10 @@ const workerPromise = new Promise<WorkerMultiDispatch | null>(resolve => {
resolve(workers); resolve(workers);
if (_DEV_) console.log('WebGL2 in worker is supported!'); if (_DEV_) console.log('WebGL2 in worker is supported!');
} else { } 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...'); if (_DEV_) console.log('WebGL2 in worker is not supported...');
} }
testWorker.terminate(); testWorker.terminate();
@ -70,6 +76,7 @@ const props = withDefaults(defineProps<{
width?: number; width?: number;
cover?: boolean; cover?: boolean;
forceBlurhash?: boolean; forceBlurhash?: boolean;
onlyAvgColor?: boolean; // Blurhash使
}>(), { }>(), {
transition: null, transition: null,
src: null, src: null,
@ -79,6 +86,7 @@ const props = withDefaults(defineProps<{
width: 64, width: 64,
cover: true, cover: true,
forceBlurhash: false, forceBlurhash: false,
onlyAvgColor: false,
}); });
const viewId = uuid(); const viewId = uuid();
@ -139,8 +147,8 @@ function drawImage(bitmap: CanvasImageSource) {
ctx.drawImage(bitmap, 0, 0, canvasWidth, canvasHeight); ctx.drawImage(bitmap, 0, 0, canvasWidth, canvasHeight);
} }
async function draw() { function drawAvg() {
if (!canvas.value || props.hash == null) return; if (!canvas.value || !props.hash) return;
const ctx = canvas.value.getContext('2d'); const ctx = canvas.value.getContext('2d');
if (!ctx) return; if (!ctx) return;
@ -149,25 +157,28 @@ async function draw() {
ctx.beginPath(); ctx.beginPath();
ctx.fillStyle = extractAvgColorFromBlurhash(props.hash) ?? '#888'; ctx.fillStyle = extractAvgColorFromBlurhash(props.hash) ?? '#888';
ctx.fillRect(0, 0, canvasWidth, canvasHeight); ctx.fillRect(0, 0, canvasWidth, canvasHeight);
}
const workers = await workerPromise; async function draw() {
if (workers) { if (props.hash == null) return;
workers.postMessage(
drawAvg();
if (props.onlyAvgColor) return;
const work = await canvasPromise;
if (work instanceof WorkerMultiDispatch) {
work.postMessage(
{ {
id: viewId, id: viewId,
hash: props.hash, hash: props.hash,
width: canvasWidth,
height: canvasHeight,
}, },
undefined, undefined,
); );
} else { } else {
try { try {
const work = document.createElement('canvas');
work.width = canvasWidth;
work.height = canvasHeight;
render(props.hash, work); render(props.hash, work);
ctx.drawImage(work, 0, 0, canvasWidth, canvasHeight); drawImage(work);
} catch (error) { } catch (error) {
console.error('Error occured during drawing blurhash', error); console.error('Error occured during drawing blurhash', error);
} }
@ -179,9 +190,9 @@ function workerOnMessage(event: MessageEvent) {
drawImage(event.data.bitmap as ImageBitmap); drawImage(event.data.bitmap as ImageBitmap);
} }
workerPromise.then(worker => { canvasPromise.then(work => {
if (worker) { if (work instanceof WorkerMultiDispatch) {
worker.addListener(workerOnMessage); work.addListener(workerOnMessage);
} }
draw(); draw();
@ -204,8 +215,10 @@ onMounted(() => {
}); });
onUnmounted(() => { onUnmounted(() => {
workerPromise.then(worker => { canvasPromise.then(work => {
worker?.removeListener(workerOnMessage); if (work instanceof WorkerMultiDispatch) {
work.removeListener(workerOnMessage);
}
}); });
}); });
</script> </script>

View file

@ -1,6 +1,6 @@
<template> <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"> <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"/> <MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/>
<div v-if="user.isCat" :class="[$style.ears]"> <div v-if="user.isCat" :class="[$style.ears]">
<div :class="$style.earLeft"> <div :class="$style.earLeft">
@ -24,6 +24,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { watch } from 'vue'; import { watch } from 'vue';
import * as misskey from 'misskey-js'; import * as misskey from 'misskey-js';
import MkImgWithBlurhash from '../MkImgWithBlurhash.vue';
import MkA from './MkA.vue'; import MkA from './MkA.vue';
import { getStaticImageUrl } from '@/scripts/media-proxy'; import { getStaticImageUrl } from '@/scripts/media-proxy';
import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash'; import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash';

View file

@ -1,5 +1,7 @@
import { render } from 'buraha'; import { render } from 'buraha';
const canvas = new OffscreenCanvas(64, 64);
onmessage = (event) => { onmessage = (event) => {
// console.log(event.data); // console.log(event.data);
if (!('id' in event.data && typeof event.data.id === 'string')) { 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')) { if (!('hash' in event.data && typeof event.data.hash === 'string')) {
return; return;
} }
const work = new OffscreenCanvas(event.data.width ?? 64, event.data.height ?? 64);
render(event.data.hash, work); render(event.data.hash, canvas);
const bitmap = work.transferToImageBitmap(); const bitmap = canvas.transferToImageBitmap();
postMessage({ id: event.data.id, bitmap }); postMessage({ id: event.data.id, bitmap });
}; };