diff --git a/package.json b/package.json index c6d92bfa..42b2e092 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@sentry/react": "^7.17.2", "embla-carousel-autoplay": "^7.0.3", "embla-carousel-react": "^7.0.3", + "fast-blurhash": "^1.1.1", "image-conversion": "^2.1.1", "react": "^18.2.0", "react-clock": "3.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f18ba89..fe9bf7a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,7 @@ specifiers: embla-carousel-react: ^7.0.3 eslint: ^8.26.0 eslint-config-react-app: ^7.0.1 + fast-blurhash: ^1.1.1 husky: ^8.0.1 image-conversion: ^2.1.1 prettier: ^2.7.1 @@ -45,6 +46,7 @@ dependencies: '@sentry/react': 7.17.2_react@18.2.0 embla-carousel-autoplay: 7.0.3 embla-carousel-react: 7.0.3_react@18.2.0 + fast-blurhash: 1.1.1 image-conversion: 2.1.1 react: 18.2.0 react-clock: 3.1.0_biqbaboplfbrettd7655fr4n2y @@ -3605,6 +3607,10 @@ packages: resolution: {integrity: sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==} dev: false + /fast-blurhash/1.1.1: + resolution: {integrity: sha512-U3mTWNRE92S4gKARJBbUmEpuYtnet+1BEeUf1qWrUHs54Db4+0aDqI9LBu7doCueFf2SRlfbR89bj+Cm89uw8A==} + dev: false + /fast-deep-equal/3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true diff --git a/src/components/widgets/background/Background.jsx b/src/components/widgets/background/Background.jsx index f624477c..f56def00 100644 --- a/src/components/widgets/background/Background.jsx +++ b/src/components/widgets/background/Background.jsx @@ -14,6 +14,7 @@ import { } from 'modules/helpers/background/widget'; import './scss/index.scss'; +import { decodeBlurHash } from 'fast-blurhash'; export default class Background extends PureComponent { constructor() { @@ -32,7 +33,7 @@ export default class Background extends PureComponent { }; } - setBackground() { + async setBackground() { const backgroundImage = document.getElementById('backgroundImage'); if (this.state.url !== '') { @@ -53,31 +54,60 @@ export default class Background extends PureComponent { return (backgroundImage.style.background = `url(${url})`); } - // firstly we set the background as hidden and make sure there is no background set currently - backgroundImage.classList.add('backgroundPreload'); + // // firstly we set the background as hidden and make sure there is no background set currently + // backgroundImage.classList.add('backgroundPreload'); backgroundImage.style.background = null; - // same with photo information if not using custom background - photoInformation?.classList.add('backgroundPreload'); + // // same with photo information if not using custom background + // photoInformation?.classList.add('backgroundPreload'); - // preloader for background transition, required, so it loads in nice - const preloader = document.createElement('img'); - preloader.src = url; + // // preloader for background transition, required, so it loads in nice + // const preloader = document.createElement('img'); + // preloader.src = url; - // once image has loaded, add the fade-in transition - preloader.addEventListener('load', () => { - backgroundImage.classList.remove('backgroundPreload'); + // // once image has loaded, add the fade-in transition + // preloader.addEventListener('load', () => { + // backgroundImage.classList.remove('backgroundPreload'); + // backgroundImage.classList.add('fade-in'); + + // backgroundImage.style.background = `url(${url})`; + // // remove the preloader element we created earlier + // preloader.remove(); + + // if (photoInformation) { + // photoInformation.classList.remove('backgroundPreload'); + // photoInformation.classList.add('fade-in'); + // } + // }); + + if (this.state.photoInfo.blur_hash) { + backgroundImage.style.backgroundColor = this.state.photoInfo.colour; backgroundImage.classList.add('fade-in'); - backgroundImage.style.background = `url(${url})`; - // remove the preloader element we created earlier - preloader.remove(); + const canvas = document.createElement('canvas'); + canvas.width = 32; + canvas.height = 32; + const ctx = canvas.getContext('2d'); + const imageData = ctx.createImageData(32, 32); + imageData.data.set(decodeBlurHash(this.state.photoInfo.blur_hash, 32, 32)); + ctx.putImageData(imageData, 0, 0); + // let blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')); + // const blobUrl = URL.createObjectURL(blob); + backgroundImage.style.backgroundImage = `url(${canvas.toDataURL()})`; + // backgroundImage.style.backgroundImage = `url(${blobUrl})`; + } - if (photoInformation) { - photoInformation.classList.remove('backgroundPreload'); - photoInformation.classList.add('fade-in'); - } - }); + // const img = new Image(); + // img.fetchPriority = 'high'; + // img.decoding = 'async'; + // img.src = url; + // await img.decode(); + // this.state.photoInfo.width = img.width; + // this.state.photoInfo.height = img.height; + const blobUrl = URL.createObjectURL(await (await fetch(url)).blob()); // TODO: revoke this later + backgroundImage.style.backgroundImage = `url(${blobUrl})`; + // img.remove(); + // URL.revokeObjectURL(blobUrl); } else { // custom colour backgroundImage.setAttribute('style', this.state.style); @@ -128,14 +158,14 @@ export default class Background extends PureComponent { let requestURL, data; switch (backgroundAPI) { case 'unsplash': - requestURL = `${variables.constants.API_URL}/images/unsplash?category=${apiCategories}&quality=${apiQuality}`; + requestURL = `${variables.constants.API_URL}/images/unsplash?categories=${apiCategories}&quality=${apiQuality}`; break; case 'pexels': requestURL = `${variables.constants.API_URL}/images/pexels?quality=${apiQuality}`; break; // Defaults to Mue default: - requestURL = `${variables.constants.API_URL}/images/random?category=${apiCategories}&quality=${apiQuality}`; + requestURL = `${variables.constants.API_URL}/images/random?categories=${apiCategories}&quality=${apiQuality}`; break; } @@ -172,6 +202,9 @@ export default class Background extends PureComponent { downloads: data.downloads || null, likes: data.likes || null, description: data.description || null, + colour: data.colour, + blur_hash: data.blur_hash, + pun: data.pun || null, }, }; diff --git a/src/components/widgets/background/PhotoInformation.jsx b/src/components/widgets/background/PhotoInformation.jsx index 1389477b..82f926ad 100644 --- a/src/components/widgets/background/PhotoInformation.jsx +++ b/src/components/widgets/background/PhotoInformation.jsx @@ -97,15 +97,15 @@ function PhotoInformation({ info, url, api }) { const ddgProxy = localStorage.getItem('ddgProxy') === 'true'; // get resolution - const img = new Image(); - img.onload = (event) => { - setWidth(event.target.width); - setHeight(event.target.height); - }; - img.src = - ddgProxy && !info.offline && !url.startsWith('data:') - ? variables.constants.DDG_IMAGE_PROXY + url - : url; +// const img = new Image(); +// img.onload = (event) => { +// setWidth(event.target.width); +// setHeight(event.target.height); +// }; +// img.src = +// ddgProxy && !info.offline && !url.startsWith('data:') +// ? variables.constants.DDG_IMAGE_PROXY + url +// : url; // info is still there because we want the favourite button to work if (localStorage.getItem('photoInformation') === 'false') { @@ -132,7 +132,6 @@ function PhotoInformation({ info, url, api }) { return null; } - const zoom = 12; const tile = variables.constants.API_URL + `/map?latitude=${info.latitude}&longitude=${info.longitude}`; showingPhotoMap = true;