Pick average color of chat background image
This commit is contained in:
parent
ddc138fa7f
commit
1edd1db4f6
@ -1,6 +1,8 @@
|
|||||||
import { generateSection } from "..";
|
import { generateSection } from "..";
|
||||||
|
import { averageColor } from "../../../helpers/averageColor";
|
||||||
import blur from "../../../helpers/blur";
|
import blur from "../../../helpers/blur";
|
||||||
import { deferredPromise } from "../../../helpers/cancellablePromise";
|
import { deferredPromise } from "../../../helpers/cancellablePromise";
|
||||||
|
import { rgbToHsl } from "../../../helpers/color";
|
||||||
import { attachClickEvent, findUpClassName } from "../../../helpers/dom";
|
import { attachClickEvent, findUpClassName } from "../../../helpers/dom";
|
||||||
import { AccountWallPapers, WallPaper } from "../../../layer";
|
import { AccountWallPapers, WallPaper } from "../../../layer";
|
||||||
import appDocsManager, { MyDocument } from "../../../lib/appManagers/appDocsManager";
|
import appDocsManager, { MyDocument } from "../../../lib/appManagers/appDocsManager";
|
||||||
@ -51,16 +53,28 @@ export default class AppBackgroundTab extends SliderSuperTab {
|
|||||||
const grid = document.createElement('div');
|
const grid = document.createElement('div');
|
||||||
grid.classList.add('grid');
|
grid.classList.add('grid');
|
||||||
|
|
||||||
const saveToCache = (url: string) => {
|
const saveToCache = (slug: string, url: string) => {
|
||||||
fetch(url).then(response => {
|
fetch(url).then(response => {
|
||||||
appDownloadManager.cacheStorage.save('background-image', response);
|
appDownloadManager.cacheStorage.save('backgrounds/' + slug, response);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// * https://github.com/TelegramMessenger/Telegram-iOS/blob/3d062fff78cc6b287c74e6171f855a3500c0156d/submodules/TelegramPresentationData/Sources/PresentationData.swift#L453
|
||||||
|
const highlightningColor = (pixel: Uint8ClampedArray) => {
|
||||||
|
let {h, s, l} = rgbToHsl(pixel[0], pixel[1], pixel[2]);
|
||||||
|
if(s > 0.0) {
|
||||||
|
s = Math.min(1.0, s + 0.05 + 0.1 * (1.0 - s));
|
||||||
|
}
|
||||||
|
l = Math.max(0.0, l * 0.65);
|
||||||
|
|
||||||
|
const hsla = `hsla(${h * 360}, ${s * 100}%, ${l * 100}%, .4)`;
|
||||||
|
return hsla;
|
||||||
|
};
|
||||||
|
|
||||||
|
let tempId = 0;
|
||||||
const setBackgroundDocument = (slug: string, doc: MyDocument) => {
|
const setBackgroundDocument = (slug: string, doc: MyDocument) => {
|
||||||
rootScope.settings.background.slug = slug;
|
let _tempId = ++tempId;
|
||||||
rootScope.settings.background.type = 'image';
|
const middleware = () => _tempId === tempId;
|
||||||
appStateManager.pushToState('settings', rootScope.settings);
|
|
||||||
|
|
||||||
const download = appDocsManager.downloadDoc(doc, appImManager.chat.bubbles ? appImManager.chat.bubbles.lazyLoadQueue.queueId : 0);
|
const download = appDocsManager.downloadDoc(doc, appImManager.chat.bubbles ? appImManager.chat.bubbles.lazyLoadQueue.queueId : 0);
|
||||||
|
|
||||||
@ -69,26 +83,44 @@ export default class AppBackgroundTab extends SliderSuperTab {
|
|||||||
deferred.cancel = download.cancel;
|
deferred.cancel = download.cancel;
|
||||||
|
|
||||||
download.then(() => {
|
download.then(() => {
|
||||||
if(rootScope.settings.background.slug !== slug || rootScope.settings.background.type !== 'image') {
|
if(!middleware()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onReady = (url: string) => {
|
||||||
|
//const perf = performance.now();
|
||||||
|
averageColor(url).then(pixel => {
|
||||||
|
if(!middleware()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hsla = highlightningColor(pixel);
|
||||||
|
//console.log(doc, hsla, performance.now() - perf);
|
||||||
|
|
||||||
|
rootScope.settings.background.slug = slug;
|
||||||
|
rootScope.settings.background.type = 'image';
|
||||||
|
rootScope.settings.background.highlightningColor = hsla;
|
||||||
|
document.documentElement.style.setProperty('--message-highlightning-color', rootScope.settings.background.highlightningColor);
|
||||||
|
appStateManager.pushToState('settings', rootScope.settings);
|
||||||
|
|
||||||
|
saveToCache(slug, url);
|
||||||
|
appImManager.setBackground(url).then(deferred.resolve);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
if(rootScope.settings.background.blur) {
|
if(rootScope.settings.background.blur) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
blur(doc.url, 12, 4)
|
blur(doc.url, 12, 4)
|
||||||
.then(url => {
|
.then(url => {
|
||||||
if(rootScope.settings.background.slug !== slug || rootScope.settings.background.type !== 'image') {
|
if(!middleware()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveToCache(url);
|
onReady(url);
|
||||||
return appImManager.setBackground(url);
|
});
|
||||||
})
|
|
||||||
.then(deferred.resolve);
|
|
||||||
}, 200);
|
}, 200);
|
||||||
} else {
|
} else {
|
||||||
saveToCache(doc.url);
|
onReady(doc.url);
|
||||||
appImManager.setBackground(doc.url).then(deferred.resolve);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -183,10 +215,10 @@ export default class AppBackgroundTab extends SliderSuperTab {
|
|||||||
|
|
||||||
load();
|
load();
|
||||||
|
|
||||||
console.log(doc);
|
//console.log(doc);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(accountWallpapers);
|
//console.log(accountWallpapers);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.scrollable.append(grid);
|
this.scrollable.append(grid);
|
||||||
|
41
src/helpers/averageColor.ts
Normal file
41
src/helpers/averageColor.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { renderImageFromUrl } from "../components/misc";
|
||||||
|
|
||||||
|
export const averageColor = (imageUrl: string): Promise<Uint8ClampedArray> => {
|
||||||
|
const img = document.createElement('img');
|
||||||
|
return new Promise<Uint8ClampedArray>((resolve) => {
|
||||||
|
renderImageFromUrl(img, imageUrl, () => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ratio = img.naturalWidth / img.naturalHeight;
|
||||||
|
const DIMENSIONS = 50;
|
||||||
|
if(ratio === 1) {
|
||||||
|
canvas.width = DIMENSIONS;
|
||||||
|
canvas.height = canvas.width / ratio;
|
||||||
|
} else if(ratio > 1) {
|
||||||
|
canvas.height = DIMENSIONS;
|
||||||
|
canvas.width = canvas.height / ratio;
|
||||||
|
} else {
|
||||||
|
canvas.width = canvas.height = DIMENSIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
context.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const pixel = new Array(4).fill(0);
|
||||||
|
const pixels = context.getImageData(0, 0, canvas.width, canvas.height).data;
|
||||||
|
for(let i = 0; i < pixels.length; i += 4) {
|
||||||
|
pixel[0] += pixels[i];
|
||||||
|
pixel[1] += pixels[i + 1];
|
||||||
|
pixel[2] += pixels[i + 2];
|
||||||
|
pixel[3] += pixels[i + 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
const pixelsLength = pixels.length / 4;
|
||||||
|
const outPixel = new Uint8ClampedArray(4);
|
||||||
|
outPixel[0] = pixel[0] / pixelsLength;
|
||||||
|
outPixel[1] = pixel[1] / pixelsLength;
|
||||||
|
outPixel[2] = pixel[2] / pixelsLength;
|
||||||
|
outPixel[3] = pixel[3] / pixelsLength;
|
||||||
|
resolve(outPixel);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
31
src/helpers/color.ts
Normal file
31
src/helpers/color.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
export function rgbToHsl(r: number, g: number, b: number) {
|
||||||
|
r /= 255, g /= 255, b /= 255;
|
||||||
|
let max = Math.max(r, g, b),
|
||||||
|
min = Math.min(r, g, b);
|
||||||
|
let h, s, l = (max + min) / 2;
|
||||||
|
|
||||||
|
if(max === min) {
|
||||||
|
h = s = 0; // achromatic
|
||||||
|
} else {
|
||||||
|
let d = max - min;
|
||||||
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||||
|
switch (max) {
|
||||||
|
case r:
|
||||||
|
h = (g - b) / d + (g < b ? 6 : 0);
|
||||||
|
break;
|
||||||
|
case g:
|
||||||
|
h = (b - r) / d + 2;
|
||||||
|
break;
|
||||||
|
case b:
|
||||||
|
h = (r - g) / d + 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
h /= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ({
|
||||||
|
h: h,
|
||||||
|
s: s,
|
||||||
|
l: l,
|
||||||
|
});
|
||||||
|
}
|
@ -6,6 +6,7 @@ import ListenerSetter from '../helpers/listenerSetter';
|
|||||||
import { CancellablePromise, deferredPromise } from '../helpers/cancellablePromise';
|
import { CancellablePromise, deferredPromise } from '../helpers/cancellablePromise';
|
||||||
import { pause } from '../helpers/schedulers';
|
import { pause } from '../helpers/schedulers';
|
||||||
import rootScope from '../lib/rootScope';
|
import rootScope from '../lib/rootScope';
|
||||||
|
import { DEBUG } from '../lib/mtproto/mtproto_config';
|
||||||
|
|
||||||
const ANIMATION_START_EVENT = 'event-heavy-animation-start';
|
const ANIMATION_START_EVENT = 'event-heavy-animation-start';
|
||||||
const ANIMATION_END_EVENT = 'event-heavy-animation-end';
|
const ANIMATION_END_EVENT = 'event-heavy-animation-end';
|
||||||
@ -14,16 +15,18 @@ let isAnimating = false;
|
|||||||
let heavyAnimationPromise: CancellablePromise<void> = Promise.resolve();
|
let heavyAnimationPromise: CancellablePromise<void> = Promise.resolve();
|
||||||
let promisesInQueue = 0;
|
let promisesInQueue = 0;
|
||||||
|
|
||||||
|
const log = console.log.bind(console.log, '[HEAVY-ANIMATION]:');
|
||||||
|
|
||||||
export const dispatchHeavyAnimationEvent = (promise: Promise<any>, timeout?: number) => {
|
export const dispatchHeavyAnimationEvent = (promise: Promise<any>, timeout?: number) => {
|
||||||
if(!isAnimating) {
|
if(!isAnimating) {
|
||||||
heavyAnimationPromise = deferredPromise<void>();
|
heavyAnimationPromise = deferredPromise<void>();
|
||||||
rootScope.broadcast(ANIMATION_START_EVENT);
|
rootScope.broadcast(ANIMATION_START_EVENT);
|
||||||
isAnimating = true;
|
isAnimating = true;
|
||||||
console.log('dispatchHeavyAnimationEvent: start');
|
DEBUG && log('start');
|
||||||
}
|
}
|
||||||
|
|
||||||
++promisesInQueue;
|
++promisesInQueue;
|
||||||
console.log('dispatchHeavyAnimationEvent: attach promise, length:', promisesInQueue, timeout);
|
DEBUG && log('attach promise, length:', promisesInQueue, timeout);
|
||||||
|
|
||||||
const promises = [
|
const promises = [
|
||||||
timeout !== undefined ? pause(timeout) : undefined,
|
timeout !== undefined ? pause(timeout) : undefined,
|
||||||
@ -33,14 +36,14 @@ export const dispatchHeavyAnimationEvent = (promise: Promise<any>, timeout?: num
|
|||||||
const perf = performance.now();
|
const perf = performance.now();
|
||||||
Promise.race(promises).then(() => {
|
Promise.race(promises).then(() => {
|
||||||
--promisesInQueue;
|
--promisesInQueue;
|
||||||
console.log('dispatchHeavyAnimationEvent: promise end, length:', promisesInQueue, performance.now() - perf);
|
DEBUG && log('promise end, length:', promisesInQueue, performance.now() - perf);
|
||||||
if(!promisesInQueue) {
|
if(!promisesInQueue) {
|
||||||
isAnimating = false;
|
isAnimating = false;
|
||||||
promisesInQueue = 0;
|
promisesInQueue = 0;
|
||||||
rootScope.broadcast(ANIMATION_END_EVENT);
|
rootScope.broadcast(ANIMATION_END_EVENT);
|
||||||
heavyAnimationPromise.resolve();
|
heavyAnimationPromise.resolve();
|
||||||
|
|
||||||
console.log('dispatchHeavyAnimationEvent: end');
|
DEBUG && log('end');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ export class AppImManager {
|
|||||||
const isDefaultBackground = rootScope.settings.background.blur === AppStateManager.STATE_INIT.settings.background.blur &&
|
const isDefaultBackground = rootScope.settings.background.blur === AppStateManager.STATE_INIT.settings.background.blur &&
|
||||||
rootScope.settings.background.slug === AppStateManager.STATE_INIT.settings.background.slug;
|
rootScope.settings.background.slug === AppStateManager.STATE_INIT.settings.background.slug;
|
||||||
if(!isDefaultBackground) {
|
if(!isDefaultBackground) {
|
||||||
appDownloadManager.cacheStorage.getFile('background-image').then(blob => {
|
appDownloadManager.cacheStorage.getFile('backgrounds/' + rootScope.settings.background.slug).then(blob => {
|
||||||
this.setBackground(URL.createObjectURL(blob), false);
|
this.setBackground(URL.createObjectURL(blob), false);
|
||||||
}, () => { // * if NO_ENTRY_FOUND
|
}, () => { // * if NO_ENTRY_FOUND
|
||||||
this.setBackground('');
|
this.setBackground('');
|
||||||
@ -223,6 +223,12 @@ export class AppImManager {
|
|||||||
private setSettings() {
|
private setSettings() {
|
||||||
document.documentElement.style.setProperty('--messages-text-size', rootScope.settings.messagesTextSize + 'px');
|
document.documentElement.style.setProperty('--messages-text-size', rootScope.settings.messagesTextSize + 'px');
|
||||||
|
|
||||||
|
if(rootScope.settings.background.highlightningColor) {
|
||||||
|
document.documentElement.style.setProperty('--message-highlightning-color', rootScope.settings.background.highlightningColor);
|
||||||
|
} else {
|
||||||
|
document.documentElement.style.removeProperty('--message-highlightning-color');
|
||||||
|
}
|
||||||
|
|
||||||
document.body.classList.toggle('animation-level-0', !rootScope.settings.animationsEnabled);
|
document.body.classList.toggle('animation-level-0', !rootScope.settings.animationsEnabled);
|
||||||
document.body.classList.toggle('animation-level-1', false);
|
document.body.classList.toggle('animation-level-1', false);
|
||||||
document.body.classList.toggle('animation-level-2', rootScope.settings.animationsEnabled);
|
document.body.classList.toggle('animation-level-2', rootScope.settings.animationsEnabled);
|
||||||
|
@ -58,6 +58,7 @@ export type State = Partial<{
|
|||||||
background: {
|
background: {
|
||||||
type: 'color' | 'image' | 'default',
|
type: 'color' | 'image' | 'default',
|
||||||
blur: boolean,
|
blur: boolean,
|
||||||
|
highlightningColor?: string,
|
||||||
color?: string,
|
color?: string,
|
||||||
slug?: string,
|
slug?: string,
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,6 @@ $bubble-margin: .25rem;
|
|||||||
--background-color: #fff;
|
--background-color: #fff;
|
||||||
--accent-color: $color-blue;
|
--accent-color: $color-blue;
|
||||||
--secondary-color: $color-gray;
|
--secondary-color: $color-gray;
|
||||||
--highlightning-color: rgba(77, 142, 80, .4);
|
|
||||||
|
|
||||||
&.is-highlighted, &.is-selected, /* .bubbles.is-selecting */ & {
|
&.is-highlighted, &.is-selected, /* .bubbles.is-selecting */ & {
|
||||||
&:after {
|
&:after {
|
||||||
@ -88,7 +87,7 @@ $bubble-margin: .25rem;
|
|||||||
|
|
||||||
&.is-highlighted:after {
|
&.is-highlighted:after {
|
||||||
//background-color: rgba(0, 132, 255, .3);
|
//background-color: rgba(0, 132, 255, .3);
|
||||||
background-color: var(--highlightning-color);
|
background-color: var(--message-highlightning-color);
|
||||||
|
|
||||||
body:not(.animation-level-0) & {
|
body:not(.animation-level-0) & {
|
||||||
animation: bubbleSelected 2s linear;
|
animation: bubbleSelected 2s linear;
|
||||||
@ -125,7 +124,7 @@ $bubble-margin: .25rem;
|
|||||||
|
|
||||||
&.is-selected {
|
&.is-selected {
|
||||||
&:after {
|
&:after {
|
||||||
background-color: rgba(77, 142, 80, .4);
|
background-color: var(--message-highlightning-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
body:not(.animation-level-0) & {
|
body:not(.animation-level-0) & {
|
||||||
@ -989,7 +988,7 @@ $bubble-margin: .25rem;
|
|||||||
|
|
||||||
&.is-highlighted {
|
&.is-highlighted {
|
||||||
.document-selection {
|
.document-selection {
|
||||||
background-color: var(--highlightning-color);
|
background-color: var(--message-highlightning-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
body:not(.animation-level-0) & {
|
body:not(.animation-level-0) & {
|
||||||
@ -1001,7 +1000,7 @@ $bubble-margin: .25rem;
|
|||||||
|
|
||||||
&.is-selected {
|
&.is-selected {
|
||||||
.document-selection {
|
.document-selection {
|
||||||
background-color: rgba(77, 142, 80, .4);
|
background-color: var(--message-highlightning-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
body:not(.animation-level-0) & {
|
body:not(.animation-level-0) & {
|
||||||
|
@ -56,6 +56,7 @@ $chat-padding-handhelds: .5rem;
|
|||||||
--message-handhelds-margin: 5.5625rem;
|
--message-handhelds-margin: 5.5625rem;
|
||||||
--message-beside-button-margin: 2.875rem;
|
--message-beside-button-margin: 2.875rem;
|
||||||
--message-time-background: rgba(0, 0, 0, .35);
|
--message-time-background: rgba(0, 0, 0, .35);
|
||||||
|
--message-highlightning-color: rgba(77, 142, 80, .4);
|
||||||
--messages-container-width: #{$messages-container-width};
|
--messages-container-width: #{$messages-container-width};
|
||||||
--messages-text-size: 16px;
|
--messages-text-size: 16px;
|
||||||
--messages-secondary-text-size: calc(var(--messages-text-size) - 1px);
|
--messages-secondary-text-size: calc(var(--messages-text-size) - 1px);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user