feat: Use the blurhash in Files

Signed-off-by: Louis Chemineau <louis@chmn.me>
This commit is contained in:
Louis Chemineau 2024-08-29 14:27:59 +02:00 committed by Ferdinand Thiessen
parent 19dd32962d
commit 56e4859201
No known key found for this signature in database
GPG key ID: 45FAE7268762B400
5 changed files with 76 additions and 11 deletions

View file

@ -14,16 +14,22 @@
</template>
</template>
<!-- Decorative image, should not be aria documented -->
<img v-else-if="previewUrl && backgroundFailed !== true"
ref="previewImg"
alt=""
class="files-list__row-icon-preview"
:class="{'files-list__row-icon-preview--loaded': backgroundFailed === false}"
loading="lazy"
:src="previewUrl"
@error="onBackgroundError"
@load="backgroundFailed = false">
<!-- Decorative images, should not be aria documented -->
<span v-else-if="previewUrl" class="files-list__row-icon-preview-container">
<canvas v-if="hasBlurhash && (backgroundFailed === true || !backgroundLoaded)"
ref="canvas"
class="files-list__row-icon-blurhash"
aria-hidden="true" />
<img v-if="backgroundFailed !== true"
ref="previewImg"
alt=""
class="files-list__row-icon-preview"
:class="{'files-list__row-icon-preview--loaded': backgroundFailed === false}"
loading="lazy"
:src="previewUrl"
@error="onBackgroundError"
@load="onBackgroundLoad">
</span>
<FileIcon v-else v-once />
@ -58,6 +64,7 @@ import LinkIcon from 'vue-material-design-icons/Link.vue'
import NetworkIcon from 'vue-material-design-icons/Network.vue'
import TagIcon from 'vue-material-design-icons/Tag.vue'
import PlayCircleIcon from 'vue-material-design-icons/PlayCircle.vue'
import { decode } from 'blurhash'
import CollectivesIcon from './CollectivesIcon.vue'
import FavoriteIcon from './FavoriteIcon.vue'
@ -107,6 +114,7 @@ export default Vue.extend({
data() {
return {
backgroundFailed: undefined as boolean | undefined,
backgroundLoaded: false,
}
},
@ -206,6 +214,16 @@ export default Vue.extend({
return null
},
hasBlurhash() {
return this.source.attributes['metadata-blurhash'] !== undefined
},
},
mounted() {
if (this.hasBlurhash && this.$refs.canvas) {
this.drawBlurhash()
}
},
methods: {
@ -213,17 +231,43 @@ export default Vue.extend({
reset() {
// Reset background state to cancel any ongoing requests
this.backgroundFailed = undefined
this.backgroundLoaded = false
if (this.$refs.previewImg) {
this.$refs.previewImg.src = ''
}
},
onBackgroundLoad() {
this.backgroundFailed = false
this.backgroundLoaded = true
},
onBackgroundError(event) {
// Do not fail if we just reset the background
if (event.target?.src === '') {
return
}
this.backgroundFailed = true
this.backgroundLoaded = false
},
drawBlurhash() {
const canvas = this.$refs.canvas as HTMLCanvasElement
const width = canvas.width
const height = canvas.height
const pixels = decode(this.source.attributes['metadata-blurhash'], width, height)
const ctx = canvas.getContext('2d')
if (ctx === null) {
logger.error('Cannot create context for blurhash canvas')
return
}
const imageData = ctx.createImageData(width, height)
imageData.data.set(pixels)
ctx.putImageData(imageData, 0, 0)
},
t,

View file

@ -556,11 +556,24 @@ export default defineComponent({
}
}
&-preview {
&-preview-container {
position: relative; // Needed for the blurshash to be positioned correctly
overflow: hidden;
width: var(--icon-preview-size);
height: var(--icon-preview-size);
border-radius: var(--border-radius);
}
&-blurhash {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
object-fit: cover;
}
&-preview {
// Center and contain the preview
object-fit: contain;
object-position: center;

View file

@ -66,5 +66,6 @@ registerPreviewServiceWorker()
registerDavProperty('nc:hidden', { nc: 'http://nextcloud.org/ns' })
registerDavProperty('nc:is-mount-root', { nc: 'http://nextcloud.org/ns' })
registerDavProperty('nc:metadata-blurhash', { nc: 'http://nextcloud.org/ns' })
initLivePhotos()

6
package-lock.json generated
View file

@ -38,6 +38,7 @@
"@vueuse/integrations": "^11.0.1",
"backbone": "^1.4.1",
"blueimp-md5": "^2.19.0",
"blurhash": "^2.0.5",
"browserslist-useragent-regexp": "^4.1.1",
"camelcase": "^8.0.0",
"cancelable-promise": "^4.3.1",
@ -8082,6 +8083,11 @@
"integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==",
"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/bmp-js": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",

View file

@ -68,6 +68,7 @@
"@vueuse/integrations": "^11.0.1",
"backbone": "^1.4.1",
"blueimp-md5": "^2.19.0",
"blurhash": "^2.0.5",
"browserslist-useragent-regexp": "^4.1.1",
"camelcase": "^8.0.0",
"cancelable-promise": "^4.3.1",