Telegram Web K with changes to work inside I2P https://web.telegram.i2p/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

234 lines
7.2 KiB

/*
* 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<HTMLCanvasElement>;
// private createCanvasPatternPromise: Promise<CanvasPattern>;
// private exportCanvasPatternToImagePromise: Promise<string>;
private renderImageFromUrlPromise: Promise<HTMLImageElement>;
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<string>((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');
const {width, height} = canvas;
// const perf = performance.now();
// if(context.fillStyle instanceof CanvasPattern) {
// context.clearRect(0, 0, width, height);
// }
const img = this.img;
let imageWidth = img.width, imageHeight = img.height;
// if(imageHeight < height) {
2 years ago
let patternHeight = 1480 * canvas.dpr;
// * correct
// if(+canvas.dataset.originalHeight !== height) hhh *= 2 / 3;
// * but have to make it good
if(+canvas.dataset.originalHeight !== height) patternHeight *= .875;
const ratio = patternHeight / imageHeight;
imageWidth *= ratio;
imageHeight = patternHeight;
// }
if(this.options.mask) {
context.fillStyle = '#000';
context.fillRect(0, 0, width, height);
context.globalCompositeOperation = 'destination-out';
} else {
context.globalCompositeOperation = 'source-over';
}
const d = (y: number) => {
for(let x = 0; x < width; x += imageWidth) {
context.drawImage(img, x, y, imageWidth, imageHeight);
}
};
const centerY = height / 2 - imageHeight / 2;
d(centerY);
if(centerY > 0) {
let topY = centerY;
do {
d(topY -= imageHeight);
} while(topY >= 0);
}
const endY = height - 1;
for(let bottomY = centerY + imageHeight; bottomY < endY; bottomY += imageHeight) {
d(bottomY);
}
// for(let x = 0; x < width; x += imageWidth) {
// for(let y = 0; y < height; y += imageHeight) {
// context.drawImage(img, x, y, imageWidth, imageHeight);
// }
// }
// context.fillStyle = this.pattern;
// context.fillRect(0, 0, width, height);
// console.warn('fill canvas time', performance.now() - perf);
}
public setCanvasDimensions(canvas: HTMLCanvasElement) {
const devicePixelRatio = Math.min(2, window.devicePixelRatio);
let width = this.options.width * devicePixelRatio,
height = this.options.height * devicePixelRatio;
2 years ago
canvas.dpr = devicePixelRatio;
canvas.dataset.originalHeight = '' + height;
if(mediaSizes.activeScreen === ScreenSize.large) height *= 1.5;
canvas.width = width;
canvas.height = height;
}
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<any>[] = [];
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};
} */
}