Pick average color of chat background image
This commit is contained in:
parent
ddc138fa7f
commit
1edd1db4f6
@ -1,6 +1,8 @@
|
||||
import { generateSection } from "..";
|
||||
import { averageColor } from "../../../helpers/averageColor";
|
||||
import blur from "../../../helpers/blur";
|
||||
import { deferredPromise } from "../../../helpers/cancellablePromise";
|
||||
import { rgbToHsl } from "../../../helpers/color";
|
||||
import { attachClickEvent, findUpClassName } from "../../../helpers/dom";
|
||||
import { AccountWallPapers, WallPaper } from "../../../layer";
|
||||
import appDocsManager, { MyDocument } from "../../../lib/appManagers/appDocsManager";
|
||||
@ -51,16 +53,28 @@ export default class AppBackgroundTab extends SliderSuperTab {
|
||||
const grid = document.createElement('div');
|
||||
grid.classList.add('grid');
|
||||
|
||||
const saveToCache = (url: string) => {
|
||||
const saveToCache = (slug: string, url: string) => {
|
||||
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) => {
|
||||
rootScope.settings.background.slug = slug;
|
||||
rootScope.settings.background.type = 'image';
|
||||
appStateManager.pushToState('settings', rootScope.settings);
|
||||
let _tempId = ++tempId;
|
||||
const middleware = () => _tempId === tempId;
|
||||
|
||||
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;
|
||||
|
||||
download.then(() => {
|
||||
if(rootScope.settings.background.slug !== slug || rootScope.settings.background.type !== 'image') {
|
||||
if(!middleware()) {
|
||||
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) {
|
||||
setTimeout(() => {
|
||||
blur(doc.url, 12, 4)
|
||||
.then(url => {
|
||||
if(rootScope.settings.background.slug !== slug || rootScope.settings.background.type !== 'image') {
|
||||
if(!middleware()) {
|
||||
return;
|
||||
}
|
||||
|
||||
saveToCache(url);
|
||||
return appImManager.setBackground(url);
|
||||
})
|
||||
.then(deferred.resolve);
|
||||
onReady(url);
|
||||
});
|
||||
}, 200);
|
||||
} else {
|
||||
saveToCache(doc.url);
|
||||
appImManager.setBackground(doc.url).then(deferred.resolve);
|
||||
onReady(doc.url);
|
||||
}
|
||||
});
|
||||
|
||||
@ -183,10 +215,10 @@ export default class AppBackgroundTab extends SliderSuperTab {
|
||||
|
||||
load();
|
||||
|
||||
console.log(doc);
|
||||
//console.log(doc);
|
||||
});
|
||||
|
||||
console.log(accountWallpapers);
|
||||
//console.log(accountWallpapers);
|
||||
});
|
||||
|
||||
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 { pause } from '../helpers/schedulers';
|
||||
import rootScope from '../lib/rootScope';
|
||||
import { DEBUG } from '../lib/mtproto/mtproto_config';
|
||||
|
||||
const ANIMATION_START_EVENT = 'event-heavy-animation-start';
|
||||
const ANIMATION_END_EVENT = 'event-heavy-animation-end';
|
||||
@ -14,16 +15,18 @@ let isAnimating = false;
|
||||
let heavyAnimationPromise: CancellablePromise<void> = Promise.resolve();
|
||||
let promisesInQueue = 0;
|
||||
|
||||
const log = console.log.bind(console.log, '[HEAVY-ANIMATION]:');
|
||||
|
||||
export const dispatchHeavyAnimationEvent = (promise: Promise<any>, timeout?: number) => {
|
||||
if(!isAnimating) {
|
||||
heavyAnimationPromise = deferredPromise<void>();
|
||||
rootScope.broadcast(ANIMATION_START_EVENT);
|
||||
isAnimating = true;
|
||||
console.log('dispatchHeavyAnimationEvent: start');
|
||||
DEBUG && log('start');
|
||||
}
|
||||
|
||||
++promisesInQueue;
|
||||
console.log('dispatchHeavyAnimationEvent: attach promise, length:', promisesInQueue, timeout);
|
||||
DEBUG && log('attach promise, length:', promisesInQueue, timeout);
|
||||
|
||||
const promises = [
|
||||
timeout !== undefined ? pause(timeout) : undefined,
|
||||
@ -33,14 +36,14 @@ export const dispatchHeavyAnimationEvent = (promise: Promise<any>, timeout?: num
|
||||
const perf = performance.now();
|
||||
Promise.race(promises).then(() => {
|
||||
--promisesInQueue;
|
||||
console.log('dispatchHeavyAnimationEvent: promise end, length:', promisesInQueue, performance.now() - perf);
|
||||
DEBUG && log('promise end, length:', promisesInQueue, performance.now() - perf);
|
||||
if(!promisesInQueue) {
|
||||
isAnimating = false;
|
||||
promisesInQueue = 0;
|
||||
rootScope.broadcast(ANIMATION_END_EVENT);
|
||||
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 &&
|
||||
rootScope.settings.background.slug === AppStateManager.STATE_INIT.settings.background.slug;
|
||||
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);
|
||||
}, () => { // * if NO_ENTRY_FOUND
|
||||
this.setBackground('');
|
||||
@ -223,6 +223,12 @@ export class AppImManager {
|
||||
private setSettings() {
|
||||
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-1', false);
|
||||
document.body.classList.toggle('animation-level-2', rootScope.settings.animationsEnabled);
|
||||
|
@ -58,6 +58,7 @@ export type State = Partial<{
|
||||
background: {
|
||||
type: 'color' | 'image' | 'default',
|
||||
blur: boolean,
|
||||
highlightningColor?: string,
|
||||
color?: string,
|
||||
slug?: string,
|
||||
}
|
||||
|
@ -51,7 +51,6 @@ $bubble-margin: .25rem;
|
||||
--background-color: #fff;
|
||||
--accent-color: $color-blue;
|
||||
--secondary-color: $color-gray;
|
||||
--highlightning-color: rgba(77, 142, 80, .4);
|
||||
|
||||
&.is-highlighted, &.is-selected, /* .bubbles.is-selecting */ & {
|
||||
&:after {
|
||||
@ -88,7 +87,7 @@ $bubble-margin: .25rem;
|
||||
|
||||
&.is-highlighted:after {
|
||||
//background-color: rgba(0, 132, 255, .3);
|
||||
background-color: var(--highlightning-color);
|
||||
background-color: var(--message-highlightning-color);
|
||||
|
||||
body:not(.animation-level-0) & {
|
||||
animation: bubbleSelected 2s linear;
|
||||
@ -125,7 +124,7 @@ $bubble-margin: .25rem;
|
||||
|
||||
&.is-selected {
|
||||
&:after {
|
||||
background-color: rgba(77, 142, 80, .4);
|
||||
background-color: var(--message-highlightning-color);
|
||||
}
|
||||
|
||||
body:not(.animation-level-0) & {
|
||||
@ -989,7 +988,7 @@ $bubble-margin: .25rem;
|
||||
|
||||
&.is-highlighted {
|
||||
.document-selection {
|
||||
background-color: var(--highlightning-color);
|
||||
background-color: var(--message-highlightning-color);
|
||||
}
|
||||
|
||||
body:not(.animation-level-0) & {
|
||||
@ -1001,7 +1000,7 @@ $bubble-margin: .25rem;
|
||||
|
||||
&.is-selected {
|
||||
.document-selection {
|
||||
background-color: rgba(77, 142, 80, .4);
|
||||
background-color: var(--message-highlightning-color);
|
||||
}
|
||||
|
||||
body:not(.animation-level-0) & {
|
||||
|
@ -56,6 +56,7 @@ $chat-padding-handhelds: .5rem;
|
||||
--message-handhelds-margin: 5.5625rem;
|
||||
--message-beside-button-margin: 2.875rem;
|
||||
--message-time-background: rgba(0, 0, 0, .35);
|
||||
--message-highlightning-color: rgba(77, 142, 80, .4);
|
||||
--messages-container-width: #{$messages-container-width};
|
||||
--messages-text-size: 16px;
|
||||
--messages-secondary-text-size: calc(var(--messages-text-size) - 1px);
|
||||
|
Loading…
x
Reference in New Issue
Block a user