summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLouis Chemineau <louis@chmn.me>2024-03-07 11:51:06 +0100
committerLouis Chemineau <louis@chmn.me>2024-03-07 11:57:49 +0100
commit12f6e25a7d1dad6cf3d84a373c8c393e85bf7c51 (patch)
treeb23f4199368365adbd17a378e97cf37993833cd8
parent3b436785333657c2c42e17fe0a21858d1d4a0b7e (diff)
feat: Use blurhash as preview
Signed-off-by: Louis Chemineau <louis@chmn.me>
-rw-r--r--package-lock.json6
-rw-r--r--package.json1
-rw-r--r--src/components/File.vue69
-rw-r--r--src/services/DavRequest.js1
4 files changed, 45 insertions, 32 deletions
diff --git a/package-lock.json b/package-lock.json
index 7ad17b28..82bda293 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,6 +25,7 @@
"@nextcloud/sharing": "^0.1.0",
"@nextcloud/upload": "^1.0.4",
"@nextcloud/vue": "^8.8.1",
+ "blurhash": "^2.0.5",
"camelcase": "^8.0.0",
"debounce": "^1.2.1",
"he": "^1.2.0",
@@ -6935,6 +6936,11 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/blurhash": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.5.tgz",
+ "integrity": "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w=="
+ },
"node_modules/bn.js": {
"version": "5.2.1",
"license": "MIT"
diff --git a/package.json b/package.json
index 650c1208..ebe5c136 100644
--- a/package.json
+++ b/package.json
@@ -54,6 +54,7 @@
"@nextcloud/sharing": "^0.1.0",
"@nextcloud/upload": "^1.0.4",
"@nextcloud/vue": "^8.8.1",
+ "blurhash": "^2.0.5",
"camelcase": "^8.0.0",
"debounce": "^1.2.1",
"he": "^1.2.0",
diff --git a/src/components/File.vue b/src/components/File.vue
index 82dab56e..43ef9185 100644
--- a/src/components/File.vue
+++ b/src/components/File.vue
@@ -41,6 +41,8 @@
<!-- Preload large preview for near visible files -->
<!-- Preload small preview for further away files -->
<template v-if="initialized">
+ <canvas v-if="hasBlurhash && !loadedSmall && !loadedLarge" ref="canvas" class="file__blurhash" />
+
<img v-if="!loadedLarge && (loadedSmall || (distance < 5 && !errorSmall))"
ref="imgSmall"
:key="`${file.basename}-small`"
@@ -81,11 +83,12 @@
<script>
import VideoIcon from 'vue-material-design-icons/Video.vue'
import PlayCircleIcon from 'vue-material-design-icons/PlayCircle.vue'
-import FavoriteIcon from './FavoriteIcon.vue'
+import { decode } from 'blurhash'
import { generateUrl } from '@nextcloud/router'
import { NcCheckboxRadioSwitch } from '@nextcloud/vue'
+import FavoriteIcon from './FavoriteIcon.vue'
import { isCachedPreview } from '../services/PreviewService.js'
export default {
@@ -155,6 +158,9 @@ export default {
isVisible() {
return this.distance === 0
},
+ hasBlurhash() {
+ return this.file.metadataBlurhash !== undefined
+ },
},
async mounted() {
@@ -164,6 +170,10 @@ export default {
])
this.initialized = true
+
+ await this.$nextTick() // Wait for next tick to have the canvas in the DOM
+
+ this.drawBlurhash()
},
beforeDestroy() {
@@ -211,6 +221,21 @@ export default {
return generateUrl(`/apps/photos/api/v1/preview/${this.file.fileid}?etag=${this.decodedEtag}&x=${size}&y=${size}`)
}
},
+ drawBlurhash() {
+ if (!this.hasBlurhash || !this.$refs.canvas) {
+ return
+ }
+
+ const width = this.$refs.canvas.width
+ const height = this.$refs.canvas.height
+
+ const pixels = decode(this.file.metadataBlurhash, width, height)
+
+ const ctx = this.$refs.canvas.getContext('2d')
+ const imageData = ctx.createImageData(width, height)
+ imageData.data.set(pixels)
+ ctx.putImageData(imageData, 0, 0)
+ },
},
}
@@ -242,6 +267,7 @@ export default {
outline-offset: -4px;
pointer-events: none;
}
+
.selection-checkbox {
opacity: 1;
}
@@ -254,8 +280,17 @@ export default {
outline: none; // Override global focus state.
display: flex; // Fill parent size
+ &__blurhash {
+ position: absolute;
+ top: 0;
+ height: 100%;
+ width: 100%;
+ object-fit: cover;
+ }
+
&__images {
- display: contents;
+ width: 100%;
+ height: 100%;
.icon-overlay {
position: absolute;
@@ -278,36 +313,6 @@ export default {
position: absolute;
color: transparent; /// Hide alt='' text when loading.
}
-
- .loading-overlay {
- position: absolute;
- height: 100%;
- width: 100%;
- display: flex;
- align-content: center;
- align-items: center;
- justify-content: center;
-
- svg {
- width: 70%;
- height: 70%;
- }
- }
- }
-
- &__hidden-description {
- position: absolute;
- left: -10000px;
- top: -10000px;
- width: 1px;
- height: 1px;
- overflow: hidden;
-
- &.show {
- position: initial;
- width: fit-content;
- height: fit-content;
- }
}
}
diff --git a/src/services/DavRequest.js b/src/services/DavRequest.js
index f402ac6e..28428db2 100644
--- a/src/services/DavRequest.js
+++ b/src/services/DavRequest.js
@@ -30,6 +30,7 @@ const props = `
<nc:metadata-photos-size />
<nc:metadata-photos-original_date_time />
<nc:metadata-files-live-photo />
+ <nc:metadata-blurhash/>
<nc:has-preview />
<nc:realpath />
<nc:hidden />