From 45b5cedeb7b41b43ccce893228ffb7dc01bd0bf8 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Sat, 9 Apr 2022 01:43:30 +0300 Subject: [PATCH 1/2] Resize patterns Optimize dark patterns --- src/components/chat/chat.ts | 45 +++++--- src/components/chat/patternRenderer.ts | 136 +++++++++++++++++++------ src/helpers/dom/renderImageFromUrl.ts | 2 +- src/lib/appManagers/appImManager.ts | 15 ++- src/scss/partials/_chat.scss | 2 +- 5 files changed, 151 insertions(+), 49 deletions(-) diff --git a/src/components/chat/chat.ts b/src/components/chat/chat.ts index 7c2ac709..914ea7d4 100644 --- a/src/components/chat/chat.ts +++ b/src/components/chat/chat.ts @@ -85,6 +85,7 @@ export default class Chat extends EventListenerBase<{ public patternCanvas: HTMLCanvasElement; public backgroundTempId: number; public setBackgroundPromise: Promise; + // public renderDarkPattern: () => Promise; constructor( public appImManager: AppImManager, @@ -159,6 +160,7 @@ export default class Chat extends EventListenerBase<{ this.patternRenderer = this.gradientCanvas = this.patternCanvas = + // this.renderDarkPattern = undefined; const intensity = theme.background.intensity && theme.background.intensity / 100; @@ -179,11 +181,28 @@ export default class Chat extends EventListenerBase<{ patternRenderer = this.patternRenderer = ChatBackgroundPatternRenderer.getInstance({ url, width: rect.width, - height: rect.height + height: rect.height, + mask: isDarkPattern }); patternCanvas = this.patternCanvas = patternRenderer.createCanvas(); patternCanvas.classList.add('chat-background-item-canvas', 'chat-background-item-pattern-canvas'); + + if(isDarkPattern) { + item.classList.add('is-dark'); + } + + // if(isDarkPattern) { + // this.renderDarkPattern = () => { + // return patternRenderer.exportCanvasPatternToImage(patternCanvas).then(url => { + // if(this.backgroundTempId !== tempId) { + // return; + // } + + // gradientCanvas.style.webkitMaskImage = `url(${url})`; + // }); + // }; + // } } else if(theme.background.slug) { item.classList.add('is-image'); } @@ -236,7 +255,11 @@ export default class Chat extends EventListenerBase<{ return; } - const append = [gradientCanvas, isDarkPattern ? undefined : patternCanvas].filter(Boolean); + const append = [ + gradientCanvas, + // isDarkPattern && this.renderDarkPattern ? undefined : patternCanvas + patternCanvas + ].filter(Boolean); if(append.length) { item.append(...append); } @@ -261,18 +284,16 @@ export default class Chat extends EventListenerBase<{ if(patternRenderer) { const renderPatternPromise = patternRenderer.renderToCanvas(patternCanvas); renderPatternPromise.then(() => { + if(this.backgroundTempId !== tempId) { + return; + } + let promise: Promise; - if(isDarkPattern) { - promise = patternRenderer.exportCanvasPatternToImage(patternCanvas).then(url => { - if(this.backgroundTempId !== tempId) { - return; - } - - gradientCanvas.style.webkitMaskImage = `url(${url})`; - }); - } else { + // if(isDarkPattern && this.renderDarkPattern) { + // promise = this.renderDarkPattern(); + // } else { promise = Promise.resolve(); - } + // } promise.then(cb); }); diff --git a/src/components/chat/patternRenderer.ts b/src/components/chat/patternRenderer.ts index 61fa1551..bbb444d6 100644 --- a/src/components/chat/patternRenderer.ts +++ b/src/components/chat/patternRenderer.ts @@ -4,7 +4,6 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ -import { IS_SAFARI } from "../../environment/userAgent"; import { indexOfAndSplice } from "../../helpers/array"; import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl"; import { deepEqual } from "../../helpers/object"; @@ -12,19 +11,21 @@ import { deepEqual } from "../../helpers/object"; type ChatBackgroundPatternRendererInitOptions = { url: string, width: number, - height: number + height: number, + mask?: boolean }; export default class ChatBackgroundPatternRenderer { private static INSTANCES: ChatBackgroundPatternRenderer[] = []; - private pattern: CanvasPattern; + // private pattern: CanvasPattern; private objectUrl: string; private options: ChatBackgroundPatternRendererInitOptions; private canvases: Set; - private createCanvasPatternPromise: Promise; - private exportCanvasPatternToImagePromise: Promise; - // private img: HTMLImageElement; + // private createCanvasPatternPromise: Promise; + // private exportCanvasPatternToImagePromise: Promise; + private renderImageFromUrlPromise: Promise; + private img: HTMLImageElement; constructor() { this.canvases = new Set(); @@ -45,36 +46,54 @@ export default class ChatBackgroundPatternRenderer { } public init(options: ChatBackgroundPatternRendererInitOptions) { + // if(this.options) { + // if(this.options.width !== options.width || this.options.height !== options.height) { + // this.createCanvasPatternPromise = + // this.pattern = + // this.exportCanvasPatternToImagePromise = + // undefined; + // } + // } + this.options = options; } public renderToCanvas(canvas: HTMLCanvasElement) { - return this.createCanvasPattern(canvas).then(() => { + // return this.createCanvasPattern(canvas).then(() => { + // return this.fillCanvas(canvas); + // }); + + return this.renderImageFromUrl(this.options.url).then(() => { return this.fillCanvas(canvas); }); } - private createCanvasPattern(canvas: HTMLCanvasElement) { + private renderImageFromUrl(url: string) { + if(this.renderImageFromUrlPromise) return this.renderImageFromUrlPromise; + const img = this.img = document.createElement('img'); + img.crossOrigin = 'anonymous'; + return this.renderImageFromUrlPromise = renderImageFromUrlPromise(img, url, false).then(() => img); + } + + /* private createCanvasPattern(canvas: HTMLCanvasElement) { if(this.createCanvasPatternPromise) return this.createCanvasPatternPromise; - return this.createCanvasPatternPromise = new Promise((resolve) => { - const img = document.createElement('img'); - img.crossOrigin = 'anonymous'; - renderImageFromUrlPromise(img, this.options.url, false).then(() => { - let createPatternFrom: HTMLImageElement | HTMLCanvasElement; - if(IS_SAFARI) { - const canvas = createPatternFrom = document.createElement('canvas'); - canvas.width = img.naturalWidth; - canvas.height = img.naturalHeight; - const ctx = canvas.getContext('2d'); - ctx.drawImage(img, 0, 0, canvas.width, canvas.height); - } else { - createPatternFrom = img; - } - - // this.img = img; - this.pattern = canvas.getContext('2d').createPattern(createPatternFrom, 'repeat-x'); - resolve(); - }); + return this.createCanvasPatternPromise = this.renderImageFromUrl(this.options.url).then((img) => { + let createPatternFrom: HTMLImageElement | HTMLCanvasElement; + if(IS_SAFARI) { + const canvas = createPatternFrom = document.createElement('canvas'); + canvas.width = img.naturalWidth; + canvas.height = img.naturalHeight; + const ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0, canvas.width, canvas.height); + } else { + createPatternFrom = img; + } + + const perf = performance.now(); + this.pattern = canvas.getContext('2d').createPattern(createPatternFrom, 'repeat-x'); + console.warn('creating pattern time:', performance.now() - perf); + + return this.pattern; }); } @@ -86,7 +105,7 @@ export default class ChatBackgroundPatternRenderer { resolve(newUrl); }, 'image/png'); }); - } + } */ public cleanup(canvas: HTMLCanvasElement) { this.canvases.delete(canvas); @@ -102,9 +121,34 @@ export default class ChatBackgroundPatternRenderer { public fillCanvas(canvas: HTMLCanvasElement) { const context = canvas.getContext('2d'); - context.fillStyle = this.pattern; - context.fillRect(0, 0, canvas.width, canvas.height); - // context.drawImage(this.img, 0, 0, canvas.width, canvas.height); + if(context.fillStyle instanceof CanvasPattern) { + context.clearRect(0, 0, canvas.width, canvas.height); + } + + // const perf = performance.now(); + const img = this.img; + + let imageWidth = img.width, imageHeight = img.height; + // if(imageHeight < canvas.height) { + const ratio = canvas.height / imageHeight; + imageWidth *= ratio; + imageHeight = canvas.height; + // } + + if(this.options.mask) { + context.fillStyle = '#000'; + context.fillRect(0, 0, canvas.width, canvas.height); + context.globalCompositeOperation = 'destination-out'; + } else { + context.globalCompositeOperation = 'source-over'; + } + + for(let x = 0; x < canvas.width; x += imageWidth) { + context.drawImage(img, x, 0); + } + // context.fillStyle = this.pattern; + // context.fillRect(0, 0, canvas.width, canvas.height); + // console.warn('fill canvas time', performance.now() - perf); } public setCanvasDimensions(canvas: HTMLCanvasElement) { @@ -118,4 +162,34 @@ export default class ChatBackgroundPatternRenderer { this.setCanvasDimensions(canvas); return canvas; } + + public resize(width: number, height: number) { + this.init({ + ...this.options, + width, + height + }); + + const promises: Promise[] = []; + for(const canvas of this.canvases) { + this.setCanvasDimensions(canvas); + promises.push(this.renderToCanvas(canvas)); + } + + return Promise.all(promises); + } + + public static resizeInstances(width: number, height: number) { + return Promise.all(this.INSTANCES.map(instance => instance.resize(width, height))); + } + + /* public setResizeMode(resizing: boolean) { + const canvases = Array.from(this.canvases); + const canvas = canvases[canvases.length - 1]; + canvas.style.display = resizing ? 'none' : ''; + const img = this.img; + img.style.display = resizing ? '' : 'none'; + + return {img, canvas}; + } */ } diff --git a/src/helpers/dom/renderImageFromUrl.ts b/src/helpers/dom/renderImageFromUrl.ts index 5078273c..3c3db569 100644 --- a/src/helpers/dom/renderImageFromUrl.ts +++ b/src/helpers/dom/renderImageFromUrl.ts @@ -61,7 +61,7 @@ export default function renderImageFromUrl( } export function renderImageFromUrlPromise(elem: Parameters[0], url: string, useCache?: boolean) { - return new Promise((resolve) => { + return new Promise((resolve) => { renderImageFromUrl(elem, url, resolve, useCache); }); } diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 77337141..947b0b7b 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -84,6 +84,7 @@ import htmlToSpan from '../../helpers/dom/htmlToSpan'; import getVisibleRect from '../../helpers/dom/getVisibleRect'; import { simulateClickEvent } from '../../helpers/dom/clickEvent'; import appReactionsManager from './appReactionsManager'; +import ChatBackgroundPatternRenderer from '../../components/chat/patternRenderer'; //console.log('appImManager included33!'); @@ -229,11 +230,17 @@ export class AppImManager { this.appendEmojiAnimationContainer(to); }); - const resizeBackgroundDebounced = debounce(() => { - this.setBackground(this.lastBackgroundUrl, false); - }, 200, false, true); mediaSizes.addEventListener('resize', () => { - resizeBackgroundDebounced(); + // const perf = performance.now(); + const rect = this.chatsContainer.getBoundingClientRect(); + ChatBackgroundPatternRenderer.resizeInstances(rect.width, rect.height).then(() => { + // this.log.warn('resize bg time:', performance.now() - perf); + // for(const chat of this.chats) { + // if(chat.renderDarkPattern) { + // chat.renderDarkPattern(); + // } + // } + }); }); rootScope.addEventListener('history_focus', (e) => { diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index 71e18a52..45cd408e 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -722,7 +722,7 @@ $background-transition-total-time: #{$input-transition-time - $background-transi width: 100%; } - &-pattern-canvas { + &:not(.is-dark) &-pattern-canvas { mix-blend-mode: overlay; // height: 100%; } From 65cef81086dbdcc22c6e1784f9aac35d41876b1c Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Sat, 9 Apr 2022 02:07:24 +0300 Subject: [PATCH 2/2] Fix repeating pattern by y --- src/components/chat/patternRenderer.ts | 10 +++++++--- src/scss/partials/_chat.scss | 7 +++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/chat/patternRenderer.ts b/src/components/chat/patternRenderer.ts index bbb444d6..71a40b12 100644 --- a/src/components/chat/patternRenderer.ts +++ b/src/components/chat/patternRenderer.ts @@ -6,6 +6,7 @@ import { indexOfAndSplice } from "../../helpers/array"; import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl"; +import mediaSizes, { ScreenSize } from "../../helpers/mediaSizes"; import { deepEqual } from "../../helpers/object"; type ChatBackgroundPatternRendererInitOptions = { @@ -144,7 +145,9 @@ export default class ChatBackgroundPatternRenderer { } for(let x = 0; x < canvas.width; x += imageWidth) { - context.drawImage(img, x, 0); + for(let y = 0; y < canvas.height; y += imageHeight) { + context.drawImage(img, x, y, imageWidth, imageHeight); + } } // context.fillStyle = this.pattern; // context.fillRect(0, 0, canvas.width, canvas.height); @@ -152,8 +155,9 @@ export default class ChatBackgroundPatternRenderer { } public setCanvasDimensions(canvas: HTMLCanvasElement) { - canvas.width = this.options.width * window.devicePixelRatio; - canvas.height = this.options.height * window.devicePixelRatio * 1.5; + const devicePixelRatio = Math.min(2, window.devicePixelRatio); + canvas.width = this.options.width * devicePixelRatio; + canvas.height = this.options.height * devicePixelRatio * (mediaSizes.activeScreen === ScreenSize.large ? 1.5 : 1); } public createCanvas() { diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index 45cd408e..98885672 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -686,8 +686,11 @@ $background-transition-total-time: #{$input-transition-time - $background-transi align-items: center; justify-content: center; // mix-blend-mode: overlay; - height: 150%; - top: -25%; + + @include respond-to(medium-screens) { + height: 150%; + top: -25%; + } } @include animation-level(2) {