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.
233 lines
7.2 KiB
233 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) { |
|
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; |
|
|
|
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}; |
|
} */ |
|
}
|
|
|