Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.

Commit 650b91b

Browse files
authored
feat: Add WebGL-based image processing (encodeImageToBlurhashWebgl) for better performance (#983)
1 parent 1d2b274 commit 650b91b

2 files changed

Lines changed: 86 additions & 6 deletions

File tree

src/components/drawer/components/image-detail-section.tsx

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@ import {
1010
NFormItem,
1111
NInput,
1212
NInputNumber,
13+
NSwitch,
1314
} from 'naive-ui'
1415
import type { Image as ImageModel } from '~/models/base'
1516
import type { PropType } from 'vue'
1617

17-
import { encodeImageToBlurhash, getDominantColor } from '~/utils/image'
18+
import {
19+
encodeImageToBlurhash,
20+
encodeImageToBlurhashWebgl,
21+
getDominantColor,
22+
} from '~/utils/image'
1823
import { isVideoExt, pickImagesFromMarkdown } from '~/utils/markdown'
1924

2025
export const ImageDetailSection = defineComponent({
@@ -39,6 +44,8 @@ export const ImageDetailSection = defineComponent({
3944
setup(props) {
4045
const loading = ref(false)
4146

47+
const useWebglFlag = ref(false)
48+
4249
const originImageMap = computed(() => {
4350
const map = new Map<string, ImageModel>()
4451
props.images.forEach((image) => {
@@ -130,7 +137,9 @@ export const ImageDetailSection = defineComponent({
130137
src: item.src,
131138
type: ext,
132139
accent: getDominantColor($image),
133-
blurHash: encodeImageToBlurhash($image),
140+
blurHash: useWebglFlag
141+
? encodeImageToBlurhashWebgl($image)
142+
: encodeImageToBlurhash($image),
134143
})
135144
})
136145
$image.onerror = (err) => {
@@ -151,7 +160,7 @@ export const ImageDetailSection = defineComponent({
151160
if (task.status === 'fulfilled') nextImageDimensions.push(task.value)
152161
else {
153162
message.warning(
154-
`获取图片信息失败:${task.reason.src}: ${task.reason.err}`,
163+
` 获取图片信息失败:${task.reason.src}: ${task.reason.err}`,
155164
)
156165
}
157166
})
@@ -176,7 +185,16 @@ export const ImageDetailSection = defineComponent({
176185
自动修正
177186
</NButton>
178187
</div>
179-
188+
<div class="mt-2 flex">
189+
<NSwitch
190+
value={useWebglFlag.value}
191+
onUpdateValue={(e) => void (useWebglFlag.value = e)}
192+
/>
193+
<div class="ml-2">
194+
{' '}
195+
使用 Webgl 加速图片处理(实验性,调用 GPU 计算)
196+
</div>
197+
</div>
180198
<NCollapse accordion class="mt-4">
181199
{images.value.map((image: ImageModel, index: number) => {
182200
return (
@@ -233,7 +251,7 @@ export const ImageDetailSection = defineComponent({
233251
}
234252
props.images[index].accent = n
235253
}}
236-
></NColorPicker>
254+
/>
237255
</NFormItem>
238256

239257
<NFormItem label="Blur Preview">
@@ -305,7 +323,7 @@ const BlurHashPreview = defineComponent({
305323
class="bg-cover bg-center"
306324
height={32}
307325
width={32}
308-
></canvas>
326+
/>
309327
)
310328
},
311329
})

src/utils/image.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,65 @@ export const encodeImageToBlurhash = (image: HTMLImageElement) => {
5858
const imageData = getImageData(image)
5959
return encode(imageData.data, imageData.width, imageData.height, 4, 4)
6060
}
61+
62+
export const encodeImageToBlurhashWebgl = (image: HTMLImageElement) => {
63+
const canvas = document.createElement('canvas')
64+
const gl = (canvas.getContext('webgl') ||
65+
canvas.getContext('experimental-webgl')) as WebGLRenderingContext
66+
67+
if (!gl) {
68+
throw new Error('WebGL not supported')
69+
}
70+
71+
canvas.width = image.naturalWidth
72+
canvas.height = image.naturalHeight
73+
74+
// Create a texture and bind image
75+
const texture = gl.createTexture()
76+
gl.bindTexture(gl.TEXTURE_2D, texture)
77+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image)
78+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
79+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
80+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
81+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
82+
83+
// Create a framebuffer and attach the texture
84+
const framebuffer = gl.createFramebuffer()
85+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer)
86+
gl.framebufferTexture2D(
87+
gl.FRAMEBUFFER,
88+
gl.COLOR_ATTACHMENT0,
89+
gl.TEXTURE_2D,
90+
texture,
91+
0,
92+
)
93+
94+
// Read the pixels
95+
const pixels = new Uint8Array(image.naturalWidth * image.naturalHeight * 4)
96+
gl.readPixels(
97+
0,
98+
0,
99+
image.naturalWidth,
100+
image.naturalHeight,
101+
gl.RGBA,
102+
gl.UNSIGNED_BYTE,
103+
pixels,
104+
)
105+
106+
// Resize the image to 32x32
107+
const resizedCanvas = document.createElement('canvas')
108+
resizedCanvas.width = 32
109+
resizedCanvas.height = 32
110+
const resizedCtx = resizedCanvas.getContext('2d')!
111+
const imageData = new ImageData(
112+
new Uint8ClampedArray(pixels),
113+
image.naturalWidth,
114+
image.naturalHeight,
115+
)
116+
resizedCtx.putImageData(imageData, 0, 0)
117+
resizedCtx.drawImage(resizedCanvas, 0, 0, 32, 32)
118+
const resizedImageData = resizedCtx.getImageData(0, 0, 32, 32)
119+
120+
// Encode the resized image to BlurHash
121+
return encode(resizedImageData.data, 32, 32, 4, 4)
122+
}

0 commit comments

Comments
 (0)