WebP: Rely on browser for rendering (#3862)
This commit is contained in:
parent
65f4a78527
commit
1f10dca00e
@ -11,7 +11,7 @@ import {
|
||||
} from '../../config';
|
||||
import { areSortedArraysIntersecting, unique } from '../../util/iteratees';
|
||||
import { getServerTime } from '../../util/serverTime';
|
||||
import { IS_OPUS_SUPPORTED, isWebpSupported } from '../../util/windowEnvironment';
|
||||
import { IS_OPUS_SUPPORTED } from '../../util/windowEnvironment';
|
||||
import { getGlobal } from '../index';
|
||||
import { getChatTitle, isUserId } from './chats';
|
||||
import { getUserFullName } from './users';
|
||||
@ -227,8 +227,7 @@ export function getMessageContentFilename(message: ApiMessage) {
|
||||
}
|
||||
|
||||
if (content.sticker) {
|
||||
const extension = content.sticker.isLottie ? 'tgs' : content.sticker.isVideo
|
||||
? 'webm' : isWebpSupported() ? 'webp' : 'png';
|
||||
const extension = content.sticker.isLottie ? 'tgs' : content.sticker.isVideo ? 'webm' : 'webp';
|
||||
return `${content.sticker.id}.${extension}`;
|
||||
}
|
||||
|
||||
|
||||
@ -1,46 +1,24 @@
|
||||
import { useLayoutEffect, useMemo, useState } from '../lib/teact/teact';
|
||||
import { useMemo } from '../lib/teact/teact';
|
||||
import { getGlobal } from '../global';
|
||||
|
||||
import type { ApiMessage, ApiSticker } from '../api/types';
|
||||
|
||||
import { DEBUG } from '../config';
|
||||
import { getMessageMediaThumbDataUri } from '../global/helpers';
|
||||
import { selectTheme } from '../global/selectors';
|
||||
import { EMPTY_IMAGE_DATA_URI, webpToPngBase64 } from '../util/webpToPng';
|
||||
import { isWebpSupported } from '../util/windowEnvironment';
|
||||
|
||||
export default function useThumbnail(media?: ApiMessage | ApiSticker) {
|
||||
const isMessage = media && 'content' in media;
|
||||
const thumbDataUri = isMessage ? getMessageMediaThumbDataUri(media) : media?.thumbnail?.dataUri;
|
||||
const sticker = isMessage ? media.content?.sticker : media;
|
||||
const shouldDecodeThumbnail = thumbDataUri && sticker && !isWebpSupported() && thumbDataUri.includes('image/webp');
|
||||
const [thumbnailDecoded, setThumbnailDecoded] = useState(EMPTY_IMAGE_DATA_URI);
|
||||
const id = media?.id;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!shouldDecodeThumbnail) {
|
||||
return;
|
||||
}
|
||||
|
||||
webpToPngBase64(`b64-${id}`, thumbDataUri!)
|
||||
.then(setThumbnailDecoded)
|
||||
.catch((err) => {
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
}, [id, shouldDecodeThumbnail, thumbDataUri]);
|
||||
|
||||
// TODO Find a way to update thumbnail on theme change
|
||||
const theme = selectTheme(getGlobal());
|
||||
|
||||
const dataUri = useMemo(() => {
|
||||
const uri = shouldDecodeThumbnail ? thumbnailDecoded : thumbDataUri;
|
||||
const uri = thumbDataUri;
|
||||
if (!uri || theme !== 'dark') return uri;
|
||||
|
||||
return uri.replace('<svg', '<svg fill="white"');
|
||||
}, [shouldDecodeThumbnail, thumbDataUri, thumbnailDecoded, theme]);
|
||||
}, [thumbDataUri, theme]);
|
||||
|
||||
return dataUri;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1,51 +0,0 @@
|
||||
/* eslint-disable */
|
||||
importScripts(new URL('./webp_wasm.js', import.meta.url));
|
||||
|
||||
Module.onRuntimeInitialized = async () => {
|
||||
self.postMessage({ type: 'initialized' });
|
||||
};
|
||||
|
||||
self.onmessage = (event) => {
|
||||
const { id, blob } = event.data;
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.addEventListener('loadend', () => {
|
||||
const buffer = reader.result;
|
||||
|
||||
const size = buffer.byteLength;
|
||||
const thisPtr = Module._malloc(size);
|
||||
Module.HEAPU8.set(new Uint8Array(buffer), thisPtr);
|
||||
|
||||
const getInfo = Module.cwrap('getInfo', 'number', ['number', 'number']);
|
||||
|
||||
const ptr = getInfo(thisPtr, size);
|
||||
const success = !!Module.getValue(ptr, 'i32');
|
||||
if (!success) {
|
||||
Module._free(ptr);
|
||||
Module._free(thisPtr);
|
||||
self.postMessage({
|
||||
type: 'result', id, width: 0, height: 0, result: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const width = Module.getValue(ptr + 4, 'i32');
|
||||
const height = Module.getValue(ptr + 8, 'i32');
|
||||
|
||||
Module._free(ptr);
|
||||
|
||||
const decode = Module.cwrap('decode', 'number', ['number', 'number']);
|
||||
|
||||
const resultPtr = decode(thisPtr, size);
|
||||
|
||||
const resultView = new Uint8Array(Module.HEAPU8.buffer, resultPtr, width * height * 4);
|
||||
const result = new Uint8ClampedArray(resultView);
|
||||
Module._free(resultPtr);
|
||||
Module._free(thisPtr);
|
||||
|
||||
self.postMessage({
|
||||
type: 'result', id, width, height, result,
|
||||
});
|
||||
});
|
||||
|
||||
reader.readAsArrayBuffer(blob);
|
||||
};
|
||||
@ -1,8 +0,0 @@
|
||||
const webpToPng = {
|
||||
webpToPng() {
|
||||
},
|
||||
webpToPngBase64() {
|
||||
},
|
||||
};
|
||||
|
||||
export default webpToPng;
|
||||
@ -1,22 +0,0 @@
|
||||
let isWebpSupportedCache: boolean | undefined;
|
||||
|
||||
export function isWebpSupported() {
|
||||
return Boolean(isWebpSupportedCache);
|
||||
}
|
||||
|
||||
function testWebp(): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const webp = new Image();
|
||||
// eslint-disable-next-line max-len
|
||||
webp.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
|
||||
const handleLoadOrError = () => {
|
||||
resolve(webp.height === 2);
|
||||
};
|
||||
webp.onload = handleLoadOrError;
|
||||
webp.onerror = handleLoadOrError;
|
||||
});
|
||||
}
|
||||
|
||||
testWebp().then((hasWebp) => {
|
||||
isWebpSupportedCache = hasWebp;
|
||||
});
|
||||
@ -15,9 +15,8 @@ import { callApi, cancelApiProgress } from '../api/gramjs';
|
||||
import * as cacheApi from './cacheApi';
|
||||
import { fetchBlob } from './files';
|
||||
import { oggToWav } from './oggToWav';
|
||||
import { webpToPng } from './webpToPng';
|
||||
import {
|
||||
IS_OPUS_SUPPORTED, IS_PROGRESSIVE_SUPPORTED, isWebpSupported,
|
||||
IS_OPUS_SUPPORTED, IS_PROGRESSIVE_SUPPORTED,
|
||||
} from './windowEnvironment';
|
||||
|
||||
const asCacheApiType = {
|
||||
@ -142,13 +141,6 @@ async function fetchFromCacheOrRemote(
|
||||
media = await oggToWav(media);
|
||||
}
|
||||
|
||||
if (cached.type === 'image/webp' && !isWebpSupported() && media) {
|
||||
const mediaPng = await webpToPng(url, media);
|
||||
if (mediaPng) {
|
||||
media = mediaPng;
|
||||
}
|
||||
}
|
||||
|
||||
const prepared = prepareMedia(media);
|
||||
|
||||
memoryCache.set(url, prepared);
|
||||
@ -184,15 +176,6 @@ async function fetchFromCacheOrRemote(
|
||||
mimeType = media.type;
|
||||
}
|
||||
|
||||
if (mimeType === 'image/webp' && !isWebpSupported()) {
|
||||
const blob = await fetchBlob(prepared as string);
|
||||
URL.revokeObjectURL(prepared as string);
|
||||
const media = await webpToPng(url, blob);
|
||||
if (media) {
|
||||
prepared = prepareMedia(media);
|
||||
}
|
||||
}
|
||||
|
||||
memoryCache.set(url, prepared);
|
||||
|
||||
return prepared;
|
||||
|
||||
@ -1,100 +0,0 @@
|
||||
import { blobToDataUri, dataUriToBlob } from './files';
|
||||
import { pause } from './schedulers';
|
||||
import { isWebpSupported } from './windowEnvironment';
|
||||
|
||||
const WORKER_INITIALIZATION_TIMEOUT = 2000;
|
||||
|
||||
let canvas: HTMLCanvasElement;
|
||||
let worker: IWebpWorker;
|
||||
|
||||
export const EMPTY_IMAGE_DATA_URI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk'
|
||||
+ 'YAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
|
||||
|
||||
export async function webpToPng(url: string, blob: Blob): Promise<Blob | undefined> {
|
||||
initWebpWorker();
|
||||
|
||||
while (!worker.wasmReady) {
|
||||
await pause(WORKER_INITIALIZATION_TIMEOUT);
|
||||
}
|
||||
|
||||
const { result, width, height } = await getDecodePromise(url, blob);
|
||||
if (!width || !height) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return createPng({ result, width, height });
|
||||
}
|
||||
|
||||
export async function webpToPngBase64(key: string, dataUri: string): Promise<string> {
|
||||
if (isWebpSupported() || dataUri.substr(0, 15) !== 'data:image/webp') {
|
||||
return dataUri;
|
||||
}
|
||||
|
||||
initWebpWorker();
|
||||
|
||||
const pngBlob = await webpToPng(key, dataUriToBlob(dataUri));
|
||||
|
||||
if (!pngBlob) {
|
||||
throw new Error(`Can't convert webp to png. Url: ${dataUri}`);
|
||||
}
|
||||
|
||||
return blobToDataUri(pngBlob);
|
||||
}
|
||||
|
||||
function initWebpWorker() {
|
||||
if (!worker) {
|
||||
worker = new Worker(new URL('../lib/webp/webp_wasm.worker.js', import.meta.url)) as IWebpWorker;
|
||||
worker.wasmReady = false;
|
||||
worker.onmessage = handleLibWebpMessage;
|
||||
}
|
||||
}
|
||||
|
||||
function createPng({ result, width, height }: TEncodedImage): Promise<Blob | undefined> {
|
||||
if (!canvas) {
|
||||
canvas = document.createElement('canvas');
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const img = new ImageData(result, width, height);
|
||||
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
ctx.putImageData(img, 0, 0);
|
||||
|
||||
canvas.toBlob((blob) => {
|
||||
resolve(blob ?? undefined);
|
||||
}, 'image/png', 1);
|
||||
});
|
||||
}
|
||||
|
||||
function handleLibWebpMessage(e: MessageEvent) {
|
||||
const { id } = e.data;
|
||||
switch (e.data.type) {
|
||||
case 'initialized': {
|
||||
worker.wasmReady = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'result': {
|
||||
if (worker.requests.has(id)) {
|
||||
const resolve = worker.requests.get(id)!;
|
||||
|
||||
worker.requests.delete(id);
|
||||
resolve(e.data!);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getDecodePromise(url: string, blob: Blob): Promise<TEncodedImage> {
|
||||
return new Promise((resolve) => {
|
||||
if (!worker.requests) {
|
||||
worker.requests = new Map();
|
||||
}
|
||||
|
||||
worker.requests.set(url, resolve);
|
||||
worker.postMessage({ id: url, blob });
|
||||
});
|
||||
}
|
||||
@ -4,8 +4,6 @@ import {
|
||||
PRODUCTION_HOSTNAME,
|
||||
} from '../config';
|
||||
|
||||
export * from './environmentWebp';
|
||||
|
||||
export function getPlatform() {
|
||||
const { userAgent, platform } = window.navigator;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user