diff --git a/package.json b/package.json index 7a12ec6cd..e30c36665 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@xterm/addon-fit": "0.10.0", "@xterm/xterm": "5.5.0", "ansi_up": "6.0.2", + "blurhash": "2.0.5", "buffer": "6.0.3", "canvas-confetti": "1.9.3", "class-transformer": "0.5.1", @@ -127,5 +128,6 @@ "vite-plugin-wasm": "3.3.0", "vite-tsconfig-paths": "4.3.2", "windicss": "3.5.6" - } -} + }, + "packageManager": "pnpm@9.7.1" +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e71ed843..83d806f45 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,6 +92,9 @@ importers: ansi_up: specifier: 6.0.2 version: 6.0.2 + blurhash: + specifier: 2.0.5 + version: 2.0.5 buffer: specifier: 6.0.3 version: 6.0.3 @@ -1815,6 +1818,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + blurhash@2.0.5: + resolution: {integrity: sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==} + bottleneck@2.19.5: resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} @@ -5317,6 +5323,8 @@ snapshots: binary-extensions@2.3.0: {} + blurhash@2.0.5: {} + bottleneck@2.19.5: {} brace-expansion@1.1.11: diff --git a/src/components/drawer/components/image-detail-section.tsx b/src/components/drawer/components/image-detail-section.tsx index d2d181577..e00910dc0 100644 --- a/src/components/drawer/components/image-detail-section.tsx +++ b/src/components/drawer/components/image-detail-section.tsx @@ -1,3 +1,4 @@ +import { decode } from 'blurhash' import { uniqBy } from 'lodash-es' import { NButton, @@ -10,11 +11,12 @@ import { NInput, NInputNumber, } from 'naive-ui' -import { getDominantColor } from '~/utils/image' -import { isVideoExt, pickImagesFromMarkdown } from '~/utils/markdown' import type { Image as ImageModel } from '~/models/base' import type { PropType } from 'vue' +import { getBlurHash, getDominantColor } from '~/utils/image' +import { isVideoExt, pickImagesFromMarkdown } from '~/utils/markdown' + export const ImageDetailSection = defineComponent({ props: { images: { @@ -57,6 +59,7 @@ export const ImageDetailSection = defineComponent({ width: existImageInfo?.width, type: existImageInfo?.type, accent: existImageInfo?.accent, + blurHash: existImageInfo?.blurHash, } as any }) .concat(props.images), @@ -127,6 +130,7 @@ export const ImageDetailSection = defineComponent({ src: item.src, type: ext, accent: getDominantColor($image), + blurHash: getBlurHash($image), }) }) $image.onerror = (err) => { @@ -232,6 +236,14 @@ export const ImageDetailSection = defineComponent({ > + +
+ {image.blurHash && ( + + )} +
+
+
@@ -267,3 +279,33 @@ export const ImageDetailSection = defineComponent({ ) }, }) + +const BlurHashPreview = defineComponent({ + props: { + hash: { + type: String, + required: true, + }, + }, + setup(props) { + const canvasRef = ref(null) + + onMounted(() => { + const canvas = canvasRef.value! + const ctx = canvas.getContext('2d')! + const pixels = decode(props.hash, 32, 32) + const imageData = ctx.createImageData(32, 32) + imageData.data.set(pixels) + ctx.putImageData(imageData, 0, 0) + }) + + return () => ( + + ) + }, +}) diff --git a/src/models/base.ts b/src/models/base.ts index e8ae0196f..961c1a4c2 100644 --- a/src/models/base.ts +++ b/src/models/base.ts @@ -12,6 +12,7 @@ export interface Image { type: string accent?: string src: string + blurHash?: string } export class BaseModel { diff --git a/src/utils/image.ts b/src/utils/image.ts index 474890801..316ada0aa 100644 --- a/src/utils/image.ts +++ b/src/utils/image.ts @@ -1,9 +1,10 @@ // @see https://stackoverflow.com/questions/2541481/get-average-color-of-image-via-javascript -// @ts-nocheck + +import { encode } from 'blurhash' export function getDominantColor(imageObject: HTMLImageElement) { const canvas = document.createElement('canvas'), - ctx = canvas.getContext('2d') + ctx = canvas.getContext('2d')! canvas.width = 1 canvas.height = 1 @@ -26,3 +27,20 @@ export function rgbToHex(red: number, green: number, blue: number) { export function rgbObjectToHex(rgb: { r: number; g: number; b: number }) { return rgbToHex(rgb.r, rgb.g, rgb.b) } + +export function getBlurHash(imageObject: HTMLImageElement) { + const canvas = document.createElement('canvas'), + ctx = canvas.getContext('2d')! + + canvas.width = imageObject.naturalWidth + canvas.height = imageObject.naturalHeight + + ctx.drawImage(imageObject, 0, 0) + + const imageData = ctx.getImageData(0, 0, 32, 32) + const pixels = new Uint8ClampedArray(imageData.data) + const componentX = 4 + const componentY = 4 + + return encode(pixels, 32, 32, componentX, componentY) +}