/* * https://github.com/morethanwords/tweb * Copyright (C) 2019-2021 Eduard Kuzmenko * https://github.com/morethanwords/tweb/blob/master/LICENSE */ import indexOfAndSplice from "../../helpers/array/indexOfAndSplice"; import deepEqual from "../../helpers/object/deepEqual"; import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl"; import mediaSizes, { ScreenSize } from "../../helpers/mediaSizes"; type ChatBackgroundPatternRendererInitOptions = { url: string, width: number, height: number, mask?: boolean }; export default class ChatBackgroundPatternRenderer { private static INSTANCES: ChatBackgroundPatternRenderer[] = []; // private pattern: CanvasPattern; private objectUrl: string; private options: ChatBackgroundPatternRendererInitOptions; private canvases: Set; // private createCanvasPatternPromise: Promise; // private exportCanvasPatternToImagePromise: Promise; private renderImageFromUrlPromise: Promise; private img: HTMLImageElement; constructor() { this.canvases = new Set(); } public static getInstance(options: ChatBackgroundPatternRendererInitOptions) { let instance = this.INSTANCES.find((instance) => { return deepEqual(instance.options, options); }); if(!instance) { instance = new ChatBackgroundPatternRenderer(); instance.init(options); this.INSTANCES.push(instance); } return instance; } 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.fillCanvas(canvas); // }); return this.renderImageFromUrl(this.options.url).then(() => { return this.fillCanvas(canvas); }); } 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 = 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; }); } public exportCanvasPatternToImage(canvas: HTMLCanvasElement) { if(this.exportCanvasPatternToImagePromise) return this.exportCanvasPatternToImagePromise; return this.exportCanvasPatternToImagePromise = new Promise((resolve) => { canvas.toBlob((blob) => { const newUrl = this.objectUrl = URL.createObjectURL(blob); resolve(newUrl); }, 'image/png'); }); } */ public cleanup(canvas: HTMLCanvasElement) { this.canvases.delete(canvas); if(!this.canvases.size) { indexOfAndSplice(ChatBackgroundPatternRenderer.INSTANCES, this); if(this.objectUrl) { URL.revokeObjectURL(this.objectUrl); } } } public fillCanvas(canvas: HTMLCanvasElement) { const context = canvas.getContext('2d'); 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) { 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); // console.warn('fill canvas time', performance.now() - perf); } public setCanvasDimensions(canvas: HTMLCanvasElement) { 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() { const canvas = document.createElement('canvas'); this.canvases.add(canvas); 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}; } */ }