Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace blur with average color in video backgrounds #4985

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions lib/Listener/CSPListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ public function handle(Event $event): void {
$csp->addAllowedConnectDomain($server);
}

$csp->addAllowedWorkerSrcDomain('\'self\'');

$event->addPolicy($csp);
}
}
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@nextcloud/vue": "^3.4.0",
"@nextcloud/vue-dashboard": "^1.0.1",
"attachmediastream": "^2.1.0",
"color.js": "^1.2.0",
"crypto-js": "^4.0.0",
"debounce": "^1.2.0",
"emoji-regex": "^9.2.0",
Expand Down
6 changes: 0 additions & 6 deletions src/components/CallView/Grid/Grid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@
:is-selected="isSelected(callParticipantModel)"
:fit-video="false"
:video-container-aspect-ratio="videoContainerAspectRatio"
:video-background-blur="videoBackgroundBlur"
:shared-data="sharedDatas[callParticipantModel.attributes.peerId]"
@click-video="handleClickVideo($event, callParticipantModel.attributes.peerId)" />
</template>
Expand Down Expand Up @@ -439,11 +438,6 @@ export default {
}
},

// Blur radius for each background in the grid
videoBackgroundBlur() {
return this.$store.getters.getBlurRadius(this.videoWidth, this.videoHeight)
},

stripeOpen() {
return this.$store.getters.isStripeOpen
},
Expand Down
11 changes: 2 additions & 9 deletions src/components/CallView/shared/Video.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@
<template v-if="participantUserId">
<VideoBackground
:display-name="participantName"
:user="participantUserId"
:grid-blur="videoBackgroundBlur" />
:user="participantUserId" />
<Avatar
:size="avatarSize"
:disable-menu="true"
Expand All @@ -63,8 +62,7 @@
</template>
<template v-else>
<VideoBackground
:display-name="participantName"
:grid-blur="videoBackgroundBlur" />
:display-name="participantName" />
<div
:class="guestAvatarClass"
class="avatar guest">
Expand Down Expand Up @@ -158,11 +156,6 @@ export default {
type: Boolean,
default: false,
},
// Calculated once in the grid component for each video background
videoBackgroundBlur: {
type: Number,
default: 0,
},
},

computed: {
Expand Down
195 changes: 25 additions & 170 deletions src/components/CallView/shared/VideoBackground.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,20 @@
<template>
<div class="video-backgroundbackground">
<div
ref="darkener"
class="darken">
<ResizeObserver
v-if="gridBlur === 0"
class="observer"
@notify="setBlur" />
</div>
<img
v-if="hasPicture"
ref="backgroundImage"
:src="backgroundImage"
:style="backgroundStyle"
class="video-background__picture"
alt="">
<div v-else
:style="{'background-color': backgroundColor }"
class="video-background" />
<div
ref="darkener"
class="darken" />
</div>
</template>

<script>
import { average } from 'color.js'
import axios from '@nextcloud/axios'
import usernameToColor from '@nextcloud/vue/dist/Functions/usernameToColor'
import { generateUrl } from '@nextcloud/router'
import { ResizeObserver } from 'vue-resize'
import { getBuilder } from '@nextcloud/browser-storage'
import browserCheck from '../../../mixins/browserCheck'
import blur from '../../../utils/imageBlurrer'

const browserStorage = getBuilder('nextcloud').persist().build()

Expand All @@ -68,13 +54,6 @@ function setUserHasAvatar(userId, flag) {

export default {
name: 'VideoBackground',
components: {
ResizeObserver,
},

mixins: [
browserCheck,
],

props: {
displayName: {
Expand All @@ -85,27 +64,27 @@ export default {
type: String,
default: '',
},
gridBlur: {
type: Number,
default: 0,
},
},

data() {
return {
hasPicture: false,
useCssBlurFilter: true,
blur: 0,
blurredBackgroundImage: null,
blurredBackgroundImageCache: {},
blurredBackgroundImageSource: null,
pendingGenerateBlurredBackgroundImageCount: 0,
isDestroyed: false,
}
},

computed: {
backgroundImageAverageColor() {
if (!this.backgroundImageUrl) {
return ''
}

return this.$store.getters.getCachedBackgroundImageAverageColor(this.backgroundImageUrl)
},
backgroundColor() {
if (this.hasPicture) {
return this.backgroundImageAverageColor
}

// If the prop is empty. We're not checking for the default value
// because the user's displayName might be '?'
if (!this.displayName) {
Expand All @@ -115,88 +94,39 @@ export default {
return `rgb(${color.r}, ${color.g}, ${color.b})`
}
},
backgroundImage() {
return this.useCssBlurFilter ? this.backgroundImageUrl : this.blurredBackgroundImage
},
backgroundImageUrl() {
if (!this.user) {
return null
}

return generateUrl(`avatar/${this.user}/300`)
},
backgroundBlur() {
return this.gridBlur ? this.gridBlur : this.blur
},
backgroundStyle() {
if (!this.useCssBlurFilter) {
return {}
}

return {
filter: `blur(${this.backgroundBlur}px)`,
}
},
// Special computed property to combine the properties that should be
// watched to set (or not) the blurred background image source.
backgroundImageUrlToBlur() {
if (this.useCssBlurFilter) {
return null
}

return this.backgroundImageUrl
},
// Special computed property to combine the properties that should be
// watched to generate (or not) the blurred background image.
generatedBackgroundBlur() {
if (!this.hasPicture || this.useCssBlurFilter) {
return false
}

if (!this.blurredBackgroundImageSource) {
return false
}

return this.backgroundBlur
},
},

watch: {
backgroundImageUrlToBlur: {
backgroundImageUrl: {
immediate: true,
handler() {
this.blurredBackgroundImageSource = null

if (!this.backgroundImageUrlToBlur) {
if (!this.backgroundImageUrl) {
return
}

const image = new Image()
image.onload = () => {
createImageBitmap(image).then(imageBitmap => {
this.blurredBackgroundImageSource = imageBitmap
})
}
image.src = this.backgroundImageUrlToBlur
},
},
generatedBackgroundBlur: {
immediate: true,
handler() {
if (this.generatedBackgroundBlur === false) {
if (this.backgroundImageAverageColor) {
// Already calculated, no need to do it again.
return
}

this.generateBlurredBackgroundImage()
average(this.backgroundImageUrl, { format: 'hex' }).then(color => {
this.$store.dispatch('setCachedBackgroundImageAverageColor', {
videoBackgroundId: this.backgroundImageUrl,
backgroundImageAverageColor: color,
})
})
},
},
},

async beforeMount() {
if (this.isChrome) {
this.useCssBlurFilter = false
}

if (!this.user) {
return
}
Expand All @@ -217,81 +147,6 @@ export default {
console.debug(exception)
}
},

async mounted() {
if (!this.gridBlur) {
// Initialise blur
this.setBlur({
width: this.$refs['darkener'].clientWidth,
height: this.$refs['darkener'].clientHeight,
})
}
},

beforeDestroy() {
this.isDestroyed = true
},

methods: {
// Calculate the background blur based on the height of the background element
setBlur({ width, height }) {
this.blur = this.$store.getters.getBlurRadius(width, height)
},

generateBlurredBackgroundImage() {
// Reset image source so the width and height are adjusted to
// the element rather than to the previous image being shown.
this.$refs.backgroundImage.src = ''

let width = this.$refs.backgroundImage.width
let height = this.$refs.backgroundImage.height

// Restore the current background so it is shown instead of an empty
// background while the new one is being generated.
this.$refs.backgroundImage.src = this.blurredBackgroundImage

const sourceAspectRatio = this.blurredBackgroundImageSource.width / this.blurredBackgroundImageSource.height
const canvasAspectRatio = width / height

if (canvasAspectRatio > sourceAspectRatio) {
height = width / sourceAspectRatio
} else if (canvasAspectRatio < sourceAspectRatio) {
width = height * sourceAspectRatio
}

const cacheId = this.backgroundImageUrl + '-' + width + '-' + height + '-' + this.backgroundBlur
if (this.blurredBackgroundImageCache[cacheId]) {
this.blurredBackgroundImage = this.blurredBackgroundImageCache[cacheId]

return
}

if (this.pendingGenerateBlurredBackgroundImageCount) {
this.pendingGenerateBlurredBackgroundImageCount++

return
}

this.pendingGenerateBlurredBackgroundImageCount = 1

blur(this.blurredBackgroundImageSource, width, height, this.backgroundBlur).then(image => {
if (this.isDestroyed) {
return
}

this.blurredBackgroundImage = image
this.blurredBackgroundImageCache[cacheId] = this.blurredBackgroundImage

const generateBlurredBackgroundImageCalledAgain = this.pendingGenerateBlurredBackgroundImageCount > 1

this.pendingGenerateBlurredBackgroundImageCount = 0

if (generateBlurredBackgroundImageCalledAgain) {
this.generateBlurredBackgroundImage()
}
})
},
},
}
</script>

Expand Down
Loading