From 0f3c91ee2aa75f9f1d9c410ce7f72523be136094 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Fri, 27 Nov 2020 21:09:05 +0200 Subject: [PATCH] New stickers preview (photoPathSize) Caching thumb after sticker render (with toneIndex too) --- src/components/chat/replyContainer.ts | 2 +- src/components/wrappers.ts | 68 ++++++++++++++------ src/layer.d.ts | 3 +- src/lib/appManagers/appDocsManager.ts | 62 +++++++++++++++++- src/lib/appManagers/appPhotosManager.ts | 27 ++++++++ src/lib/crypto/cryptoworker.ts | 2 + src/scripts/in/schema_additional_params.json | 3 +- src/scss/partials/popups/_stickers.scss | 3 + src/scss/style.scss | 14 ++-- 9 files changed, 155 insertions(+), 29 deletions(-) diff --git a/src/components/chat/replyContainer.ts b/src/components/chat/replyContainer.ts index fa665e99..b9a89b6a 100644 --- a/src/components/chat/replyContainer.ts +++ b/src/components/chat/replyContainer.ts @@ -42,7 +42,7 @@ export function wrapReplyDivAndCaption(options: { div: mediaEl, lazyLoadQueue: appImManager.lazyLoadQueue, group: CHAT_ANIMATION_GROUP, - onlyThumb: media.document.sticker == 2, + //onlyThumb: media.document.sticker == 2, width: 32, height: 32 }); diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index 282c33ba..f2c9c7e4 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -564,8 +564,8 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o const toneIndex = emoji ? getEmojiToneIndex(emoji) : -1; - if(doc.thumbs?.length && !div.firstElementChild && (!doc.downloaded || stickerType == 2 || onlyThumb) && toneIndex <= 0/* && doc.thumbs[0]._ != 'photoSizeEmpty' */) { - const thumb = doc.thumbs[0]; + if((doc.thumbs?.length || doc.stickerCachedThumbs) && !div.firstElementChild && (!doc.downloaded || stickerType == 2 || onlyThumb)/* && doc.thumbs[0]._ != 'photoSizeEmpty' */) { + const thumb = doc.stickerCachedThumbs && doc.stickerCachedThumbs[toneIndex] || doc.thumbs[0]; //console.log('wrap sticker', thumb, div); @@ -580,23 +580,29 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o img = new Image(); renderImageFromUrl(img, thumb.url, afterRender); } else if('bytes' in thumb) { - img = new Image(); - - if((webpWorkerController.isWebpSupported() || doc.pFlags.stickerThumbConverted || thumb.url)/* && false */) { - renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true), afterRender); - } else { - webpWorkerController.convert(doc.id, thumb.bytes as Uint8Array).then(bytes => { - thumb.bytes = bytes; - doc.pFlags.stickerThumbConverted = true; - - if(middleware && !middleware()) return; - - if(!div.childElementCount) { - renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true), afterRender); - } - }).catch(() => {}); + if(thumb._ == 'photoPathSize') { + //if(!doc.w) console.error('no w', doc); + div.innerHTML = ` + + `; + } else if(toneIndex <= 0) { + img = new Image(); + if((webpWorkerController.isWebpSupported() || doc.pFlags.stickerThumbConverted || thumb.url)/* && false */) { + renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true), afterRender); + } else { + webpWorkerController.convert(doc.id, thumb.bytes as Uint8Array).then(bytes => { + thumb.bytes = bytes; + doc.pFlags.stickerThumbConverted = true; + + if(middleware && !middleware()) return; + + if(!div.childElementCount) { + renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true), afterRender); + } + }).catch(() => {}); + } } - } else if(stickerType == 2 && (withThumb || onlyThumb)) { + } else if(stickerType == 2 && (withThumb || onlyThumb) && toneIndex <= 0) { img = new Image(); const load = () => { @@ -637,6 +643,9 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o console.log('loaded sticker:', doc, div); } */ + //await new Promise((resolve) => setTimeout(resolve, 500)); + //return; + //console.time('download sticker' + doc.id); //appDocsManager.downloadDocNew(doc.id).promise.then(res => res.json()).then(async(json) => { @@ -649,7 +658,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o //console.log('loaded sticker:', doc, div/* , blob */); if(middleware && !middleware()) return; - let animation = await LottieLoader.loadAnimationWorker/* loadAnimation */({ + let animation = await LottieLoader.loadAnimationWorker({ container: div, loop: loop && !emoji, autoplay: play, @@ -661,12 +670,29 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o //const deferred = deferredPromise(); animation.addListener('firstFrame', () => { - if(div.firstElementChild && div.firstElementChild.tagName == 'IMG') { - div.firstElementChild.remove(); + const element = div.firstElementChild; + const needFadeIn = !element || element.tagName === 'svg'; + + const cb = () => { + if(element && element != animation.canvas) { + element.remove(); + } + }; + + if(!needFadeIn) { + cb(); } else { animation.canvas.classList.add('fade-in'); + + if(element) { + setTimeout(() => { + cb(); + }, element.tagName === 'svg' ? 50 : 200); + } } + appDocsManager.saveLottiePreview(doc, animation.canvas, toneIndex); + //deferred.resolve(); }, true); diff --git a/src/layer.d.ts b/src/layer.d.ts index d11c3a4a..021bc267 100644 --- a/src/layer.d.ts +++ b/src/layer.d.ts @@ -2927,7 +2927,7 @@ export namespace Document { date: number, mime_type: string, size: number, - thumbs?: Array, + thumbs?: Array, video_thumbs?: Array, dc_id: number, attributes: Array, @@ -2948,6 +2948,7 @@ export namespace Document { pFlags?: Partial<{ stickerThumbConverted?: true, }>, + stickerCachedThumbs?: {[toneIndex: number]: {url: string, w: number, h: number}}, animated?: boolean, supportsStreaming?: boolean }; diff --git a/src/lib/appManagers/appDocsManager.ts b/src/lib/appManagers/appDocsManager.ts index 99c47bfa..67b5644b 100644 --- a/src/lib/appManagers/appDocsManager.ts +++ b/src/lib/appManagers/appDocsManager.ts @@ -1,6 +1,5 @@ import { FileURLType, getFileNameByLocation, getFileURL } from '../../helpers/fileName'; import { safeReplaceArrayInObject, defineNotNumerableProperties, isObject } from '../../helpers/object'; -import { isSafari } from '../../helpers/userAgent'; import { Document, InputFileLocation, PhotoSize } from '../../layer'; import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config'; import referenceDatabase, { ReferenceContext } from '../mtproto/referenceDatabase'; @@ -16,6 +15,7 @@ export type MyDocument = Document.document; class AppDocsManager { private docs: {[docID: string]: MyDocument} = {}; + private savingLottiePreview: {[docID: string]: true} = {}; public saveDoc(doc: Document, context?: ReferenceContext): MyDocument { if(doc._ == 'documentEmpty') { @@ -326,6 +326,66 @@ class AppDocsManager { return download; } + public saveLottiePreview(doc: MyDocument, canvas: HTMLCanvasElement, toneIndex: number) { + const key = doc.id + '-' + toneIndex; + if(this.savingLottiePreview[key]/* || true */) return; + + if(!doc.stickerCachedThumbs) { + defineNotNumerableProperties(doc, ['stickerCachedThumbs']); + doc.stickerCachedThumbs = {}; + } + + const thumb = doc.stickerCachedThumbs[toneIndex]; + if(thumb && thumb.w >= canvas.width && thumb.h >= canvas.height) { + return; + } + + /* if(doc.thumbs.find(t => t._ == 'photoStrippedSize') + || (doc.stickerCachedThumb || (doc.stickerSavedThumbWidth >= canvas.width && doc.stickerSavedThumbHeight >= canvas.height))) { + return; + } */ + + this.savingLottiePreview[key] = true; + canvas.toBlob((blob) => { + //console.log('got lottie preview', doc, blob, URL.createObjectURL(blob)); + + const thumb = { + url: URL.createObjectURL(blob), + w: canvas.width, + h: canvas.height + }; + + doc.stickerCachedThumbs[toneIndex] = thumb; + + delete this.savingLottiePreview[key]; + + /* const reader = new FileReader(); + reader.onloadend = (e) => { + const uint8 = new Uint8Array(e.target.result as ArrayBuffer); + const thumb: PhotoSize.photoStrippedSize = { + _: 'photoStrippedSize', + bytes: uint8, + type: 'i' + }; + + doc.stickerSavedThumbWidth = canvas.width; + doc.stickerSavedThumbHeight = canvas.width; + + defineNotNumerableProperties(thumb, ['url']); + thumb.url = URL.createObjectURL(blob); + doc.thumbs.findAndSplice(t => t._ == thumb._); + doc.thumbs.unshift(thumb); + + if(!webpWorkerController.isWebpSupported()) { + doc.pFlags.stickerThumbConverted = true; + } + + delete this.savingLottiePreview[doc.id]; + }; + reader.readAsArrayBuffer(blob); */ + }); + } + public saveDocFile(doc: MyDocument) { const options = this.getFileDownloadOptions(doc); return appDownloadManager.downloadToDisc(options, doc.file_name); diff --git a/src/lib/appManagers/appPhotosManager.ts b/src/lib/appManagers/appPhotosManager.ts index fe29d173..37ae88e8 100644 --- a/src/lib/appManagers/appPhotosManager.ts +++ b/src/lib/appManagers/appPhotosManager.ts @@ -158,6 +158,33 @@ export class AppPhotosManager { return URL.createObjectURL(blob); } + /** + * https://core.telegram.org/api/files#vector-thumbnails + */ + public getPathFromPhotoPathSize(size: PhotoSize.photoPathSize) { + const bytes = size.bytes; + const lookup = "AACAAAAHAAALMAAAQASTAVAAAZaacaaaahaaalmaaaqastava.az0123456789-,"; + + let path = 'M'; + for(let i = 0, length = bytes.length; i < length; ++i) { + const num = bytes[i]; + + if(num >= (128 + 64)) { + path += lookup[num - 128 - 64]; + } else { + if(num >= 128) { + path += ','; + } else if(num >= 64) { + path += '-'; + } + path += '' + (num & 63); + } + } + path += 'z'; + + return path; + } + public getPreviewURLFromThumb(thumb: PhotoSize.photoCachedSize | PhotoSize.photoStrippedSize, isSticker = false) { return thumb.url ?? (defineNotNumerableProperties(thumb, ['url']), thumb.url = this.getPreviewURLFromBytes(thumb.bytes, isSticker)); } diff --git a/src/lib/crypto/cryptoworker.ts b/src/lib/crypto/cryptoworker.ts index 94837b00..109e2a14 100644 --- a/src/lib/crypto/cryptoworker.ts +++ b/src/lib/crypto/cryptoworker.ts @@ -1,3 +1,4 @@ +import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config'; import CryptoWorkerMethods from './crypto_methods'; type Task = { @@ -124,5 +125,6 @@ class CryptoWorker extends CryptoWorkerMethods { } const cryptoWorker = new CryptoWorker(); +MOUNT_CLASS_TO && (MOUNT_CLASS_TO.CryptoWorker = cryptoWorker); //(window as any).CryptoWorker = cryptoWorker; export default cryptoWorker; diff --git a/src/scripts/in/schema_additional_params.json b/src/scripts/in/schema_additional_params.json index f4ba7da3..2021f937 100644 --- a/src/scripts/in/schema_additional_params.json +++ b/src/scripts/in/schema_additional_params.json @@ -1,7 +1,7 @@ [{ "predicate": "document", "params": [ - {"name": "thumbs", "type": "Array"}, + {"name": "thumbs", "type": "Array"}, {"name": "type", "type": "'gif' | 'sticker' | 'audio' | 'voice' | 'video' | 'round' | 'photo'"}, {"name": "h", "type": "number"}, {"name": "w", "type": "number"}, @@ -17,6 +17,7 @@ {"name": "stickerEmojiRaw", "type": "string"}, {"name": "stickerSetInput", "type": "InputStickerSet.inputStickerSetID"}, {"name": "stickerThumbConverted", "type": "true"}, + {"name": "stickerCachedThumbs", "type": "{[toneIndex: number]: {url: string, w: number, h: number}}"}, {"name": "animated", "type": "boolean"}, {"name": "supportsStreaming", "type": "boolean"} ] diff --git a/src/scss/partials/popups/_stickers.scss b/src/scss/partials/popups/_stickers.scss index cf05c82c..7db0e81e 100644 --- a/src/scss/partials/popups/_stickers.scss +++ b/src/scss/partials/popups/_stickers.scss @@ -65,6 +65,7 @@ margin-bottom: 2px; justify-self: center; cursor: pointer; + position: relative; @include respond-to(handhelds) { margin-bottom: 8px; @@ -78,6 +79,8 @@ img { max-width: 100%; max-height: 100%; + width: 100%; + height: 100%; } } } diff --git a/src/scss/style.scss b/src/scss/style.scss index c25c298d..6b6edee9 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -699,15 +699,21 @@ img.emoji { pointer-events: none; } -.rlottie { +.rlottie, .rlottie-vector { + position: absolute; max-width: 100%; max-height: 100%; width: 100%; height: 100%; + z-index: 1; +} - &.fade-in { - animation: fade-in-opacity .2s ease forwards; - } +.rlottie.fade-in { + animation: fade-in-opacity .2s ease forwards; +} + +.rlottie-vector { + fill: rgba(0, 0, 0, .08); } .fade-in-transition {