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:
parent
a1327fa9e1
commit
734c41aba5
3 changed files with 39 additions and 23 deletions
|
@ -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>
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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 });
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue