Revert "[Perf] Use canvas for faster blur (#1100)"

This reverts commit 27cc33b5541e6f9fe27cf74220e8629a5966d339.
This commit is contained in:
Alexander Zinchuk 2021-05-24 16:01:21 +03:00
parent 3e15d53c2f
commit e1cf508eca
12 changed files with 74 additions and 129 deletions

View File

@ -20,16 +20,12 @@
grid-column-end: span 2;
}
.thumbnail {
.preview {
background-size: cover !important;
background: transparent no-repeat center;
}
.thumbnail ~ video {
position: absolute;
}
.thumbnail, video {
.preview, video {
width: 100%;
height: 100%;
object-fit: cover;

View File

@ -8,9 +8,9 @@ import buildClassName from '../../util/buildClassName';
import { ObserveFn, useIsIntersecting } from '../../hooks/useIntersectionObserver';
import useMedia from '../../hooks/useMedia';
import useTransitionForMedia from '../../hooks/useTransitionForMedia';
import useBlur from '../../hooks/useBlur';
import useVideoCleanup from '../../hooks/useVideoCleanup';
import useBuffering from '../../hooks/useBuffering';
import useCanvasBlur from '../../hooks/useCanvasBlur';
import Spinner from '../ui/Spinner';
@ -31,15 +31,15 @@ const GifButton: FC<OwnProps> = ({
// eslint-disable-next-line no-null/no-null
const videoRef = useRef<HTMLVideoElement>(null);
const hasThumbnail = gif.thumbnail && !!gif.thumbnail.dataUri;
const localMediaHash = `gif${gif.id}`;
const isIntersecting = useIsIntersecting(ref, observeIntersection);
const loadAndPlay = isIntersecting && !isDisabled;
const previewBlobUrl = useMedia(`${localMediaHash}?size=m`, !loadAndPlay, ApiMediaFormat.BlobUrl);
const thumbRef = useCanvasBlur(gif.thumbnail && gif.thumbnail.dataUri, Boolean(previewBlobUrl));
const thumbDataUri = useBlur(gif.thumbnail && gif.thumbnail.dataUri, Boolean(previewBlobUrl));
const previewData = previewBlobUrl || thumbDataUri;
const videoData = useMedia(localMediaHash, !loadAndPlay, ApiMediaFormat.BlobUrl);
const shouldRenderVideo = Boolean(loadAndPlay && videoData);
const { transitionClassNames } = useTransitionForMedia(hasThumbnail || previewBlobUrl || videoData, 'slow');
const { transitionClassNames } = useTransitionForMedia(previewData || videoData, 'slow');
const { isBuffered, bufferingHandlers } = useBuffering(true);
const shouldRenderSpinner = loadAndPlay && !isBuffered;
@ -66,20 +66,14 @@ const GifButton: FC<OwnProps> = ({
className={className}
onClick={handleClick}
>
{hasThumbnail && (
<canvas
ref={thumbRef}
className="thumbnail"
{previewData && !shouldRenderVideo && (
<div
className="preview"
// @ts-ignore
style={`background-image: url(${previewData});`}
/>
)}
{!hasThumbnail && previewBlobUrl && (
<img
src={previewBlobUrl}
alt=""
className="thumbnail"
/>
)}
{(shouldRenderVideo || previewBlobUrl) && (
{shouldRenderVideo && (
<video
ref={videoRef}
autoPlay
@ -87,6 +81,7 @@ const GifButton: FC<OwnProps> = ({
muted
playsInline
preload="none"
poster={previewData}
// eslint-disable-next-line react/jsx-props-no-spreading
{...bufferingHandlers}
>
@ -94,7 +89,7 @@ const GifButton: FC<OwnProps> = ({
</video>
)}
{shouldRenderSpinner && (
<Spinner color={previewBlobUrl || hasThumbnail ? 'white' : 'black'} />
<Spinner color={previewData ? 'white' : 'black'} />
)}
</div>
);

View File

@ -18,7 +18,7 @@
transform: scale(1);
transition: transform .15s ease;
img, canvas {
img {
position: absolute;
left: 0;
top: 0;

View File

@ -13,7 +13,7 @@ import useMedia from '../../../hooks/useMedia';
import useMediaWithDownloadProgress from '../../../hooks/useMediaWithDownloadProgress';
import useShowTransition from '../../../hooks/useShowTransition';
import usePrevious from '../../../hooks/usePrevious';
import useCanvasBlur from '../../../hooks/useCanvasBlur';
import useBlur from '../../../hooks/useBlur';
import ProgressSpinner from '../../ui/ProgressSpinner';
@ -25,6 +25,8 @@ type OwnProps = {
onClick: (slug: string) => void;
};
const ANIMATION_DURATION = 300;
const WallpaperTile: FC<OwnProps> = ({
wallpaper,
isSelected,
@ -35,10 +37,10 @@ const WallpaperTile: FC<OwnProps> = ({
const localMediaHash = `wallpaper${document.id!}`;
const localBlobUrl = document.previewBlobUrl;
const previewBlobUrl = useMedia(`${localMediaHash}?size=m`);
const thumbRef = useCanvasBlur(
const thumbDataUri = useBlur(
document.thumbnail && document.thumbnail.dataUri,
Boolean(previewBlobUrl),
true,
ANIMATION_DURATION,
);
const {
shouldRenderThumb, shouldRenderFullMedia, transitionClassNames,
@ -86,9 +88,10 @@ const WallpaperTile: FC<OwnProps> = ({
<div className={className} onClick={handleClick}>
<div className="media-inner">
{shouldRenderThumb && (
<canvas
ref={thumbRef}
<img
src={thumbDataUri}
className="thumbnail"
alt=""
/>
)}
{shouldRenderFullMedia && (

View File

@ -16,8 +16,8 @@ import { ObserveFn, useIsIntersecting } from '../../../hooks/useIntersectionObse
import useMediaWithDownloadProgress from '../../../hooks/useMediaWithDownloadProgress';
import useTransitionForMedia from '../../../hooks/useTransitionForMedia';
import useShowTransition from '../../../hooks/useShowTransition';
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
import usePrevious from '../../../hooks/usePrevious';
import useBlurredMediaThumb from './hooks/useBlurredMediaThumb';
import buildClassName from '../../../util/buildClassName';
import getCustomAppendixBg from './helpers/getCustomAppendixBg';
import { calculateMediaDimensions } from './helpers/mediaDimensions';
@ -69,7 +69,7 @@ const Photo: FC<OwnProps> = ({
mediaData, downloadProgress,
} = useMediaWithDownloadProgress(getMessageMediaHash(message, size), !shouldDownload);
const fullMediaData = localBlobUrl || mediaData;
const thumbRef = useBlurredMediaThumbRef(message, fullMediaData);
const thumbDataUri = useBlurredMediaThumb(message, fullMediaData);
const {
isUploading, isTransferring, transferProgress,
@ -122,6 +122,11 @@ const Photo: FC<OwnProps> = ({
width === height && 'square-image',
);
const thumbClassName = buildClassName(
'thumbnail',
!thumbDataUri && 'empty',
);
const style = dimensions
? `width: ${width}px; height: ${height}px; left: ${dimensions.x}px; top: ${dimensions.y}px;`
: '';
@ -136,11 +141,12 @@ const Photo: FC<OwnProps> = ({
onClick={isUploading ? undefined : handleClick}
>
{shouldRenderThumb && (
<canvas
ref={thumbRef}
className="thumbnail"
// @ts-ignore teact feature
style={`width: ${width}px; height: ${height}px`}
<img
src={thumbDataUri}
className={thumbClassName}
width={width}
height={height}
alt=""
/>
)}
{shouldRenderFullMedia && (

View File

@ -20,8 +20,8 @@ import useBuffering from '../../../hooks/useBuffering';
import buildClassName from '../../../util/buildClassName';
import useHeavyAnimationCheckForVideo from '../../../hooks/useHeavyAnimationCheckForVideo';
import useVideoCleanup from '../../../hooks/useVideoCleanup';
import useBlurredMediaThumb from './hooks/useBlurredMediaThumb';
import usePauseOnInactive from './hooks/usePauseOnInactive';
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
import safePlay from '../../../util/safePlay';
import ProgressSpinner from '../../ui/ProgressSpinner';
@ -74,7 +74,7 @@ const RoundVideo: FC<OwnProps> = ({
getMessageMediaFormat(message, 'inline'),
lastSyncTime,
);
const thumbRef = useBlurredMediaThumbRef(message, mediaData);
const thumbDataUri = useBlurredMediaThumb(message, mediaData);
const { isBuffered, bufferingHandlers } = useBuffering();
const isTransferring = isDownloadAllowed && !isBuffered;
@ -183,11 +183,12 @@ const RoundVideo: FC<OwnProps> = ({
>
{shouldRenderThumb && (
<div className="thumbnail-wrapper">
<canvas
ref={thumbRef}
<img
src={thumbDataUri}
className="thumbnail"
// @ts-ignore teact feature
style={`width: ${ROUND_VIDEO_DIMENSIONS}px; height: ${ROUND_VIDEO_DIMENSIONS}px`}
width={ROUND_VIDEO_DIMENSIONS}
height={ROUND_VIDEO_DIMENSIONS}
alt=""
/>
</div>
)}
@ -203,6 +204,7 @@ const RoundVideo: FC<OwnProps> = ({
muted={!isActivated}
loop={!isActivated}
playsInline
poster={thumbDataUri}
onEnded={isActivated ? stopPlaying : undefined}
// eslint-disable-next-line react/jsx-props-no-spreading
{...bufferingHandlers}

View File

@ -24,9 +24,9 @@ import useTransitionForMedia from '../../../hooks/useTransitionForMedia';
import usePrevious from '../../../hooks/usePrevious';
import useBuffering from '../../../hooks/useBuffering';
import useHeavyAnimationCheckForVideo from '../../../hooks/useHeavyAnimationCheckForVideo';
import useBlurredMediaThumb from './hooks/useBlurredMediaThumb';
import useVideoCleanup from '../../../hooks/useVideoCleanup';
import usePauseOnInactive from './hooks/usePauseOnInactive';
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
import ProgressSpinner from '../../ui/ProgressSpinner';
@ -76,7 +76,7 @@ const Video: FC<OwnProps> = ({
getMessageMediaFormat(message, 'pictogram'),
lastSyncTime,
);
const thumbRef = useBlurredMediaThumbRef(message);
const thumbDataUri = useBlurredMediaThumb(message, previewBlobUrl);
const { mediaData, downloadProgress } = useMediaWithDownloadProgress(
getMessageMediaHash(message, 'inline'),
!shouldDownload,
@ -84,6 +84,7 @@ const Video: FC<OwnProps> = ({
lastSyncTime,
);
const previewMediaData = previewBlobUrl || thumbDataUri;
const fullMediaData = localBlobUrl || mediaData;
const isInline = Boolean(canPlayInline && isIntersecting && fullMediaData);
@ -131,10 +132,9 @@ const Video: FC<OwnProps> = ({
}, [isUploading, canPlayInline, fullMediaData, isPlayAllowed, onClick, onCancelUpload, message]);
const className = buildClassName('media-inner dark', !isUploading && 'interactive');
const thumbClassName = buildClassName('thumbnail', !previewMediaData && 'empty');
const videoClassName = buildClassName('full-media', transitionClassNames);
const videoStyle = previewBlobUrl
? `background-image: url(${previewBlobUrl}); background-size: cover`
: 'background: transparent';
const videoStyle = previewMediaData ? `background-image: url(${previewMediaData}); background-size: cover` : '';
const style = dimensions
? `width: ${width}px; height: ${height}px; left: ${dimensions.x}px; top: ${dimensions.y}px;`
@ -154,25 +154,15 @@ const Video: FC<OwnProps> = ({
style={style}
onClick={isUploading ? undefined : handleClick}
>
{((!isInline || shouldRenderThumb) && !previewBlobUrl)
&& (
<canvas
ref={thumbRef}
className="thumbnail"
// @ts-ignore teact feature
style={`width: ${width}px; height: ${height}px;`}
/>
)}
{previewBlobUrl && fullMediaData && (
{(shouldRenderThumb || !isInline) && (
<img
src={previewBlobUrl}
className="thumbnail"
// @ts-ignore teact feature
style={`width: ${width}px; height: ${height}px;`}
src={previewMediaData}
className={thumbClassName}
width={width}
height={height}
alt=""
/>
)}
{shouldRenderInlineVideo && (
<video
ref={videoRef}

View File

@ -0,0 +1,14 @@
import { ApiMessage } from '../../../../api/types';
import { LAYERS_TRANSITION_DURATION } from '../../../../config';
import { IS_MOBILE_SCREEN } from '../../../../util/environment';
import { getMessageMediaThumbDataUri } from '../../../../modules/helpers';
import useBlur from '../../../../hooks/useBlur';
export default function useBlurredMediaThumb(message: ApiMessage, fullMediaData?: string) {
return useBlur(
getMessageMediaThumbDataUri(message),
Boolean(fullMediaData),
IS_MOBILE_SCREEN ? LAYERS_TRANSITION_DURATION : undefined,
);
}

View File

@ -1,13 +0,0 @@
import { ApiMessage } from '../../../../api/types';
import { IS_CANVAS_FILTER_SUPPORTED, IS_MOBILE_SCREEN } from '../../../../util/environment';
import { getMessageMediaThumbDataUri } from '../../../../modules/helpers';
import useCanvasBlur from '../../../../hooks/useCanvasBlur';
export default function useBlurredMediaThumbRef(message: ApiMessage, fullMediaData?: string) {
return useCanvasBlur(
getMessageMediaThumbDataUri(message),
Boolean(fullMediaData),
IS_MOBILE_SCREEN && !IS_CANVAS_FILTER_SUPPORTED,
);
}

View File

@ -1,53 +0,0 @@
import { useEffect, useRef } from '../lib/teact/teact';
import fastBlur from '../lib/fastBlur';
import useForceUpdate from './useForceUpdate';
import { IS_CANVAS_FILTER_SUPPORTED } from '../util/environment';
const RADIUS = 2;
const ITERATIONS = 2;
export default function useCanvasBlur(dataUri?: string, isDisabled = false, withRaf?: boolean) {
// eslint-disable-next-line no-null/no-null
const canvasRef = useRef<HTMLCanvasElement>(null);
const forceUpdate = useForceUpdate();
useEffect(() => {
const canvas = canvasRef.current;
if (!dataUri || !canvas || isDisabled) {
return;
}
const img = new Image();
const processBlur = () => {
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d', { alpha: false })!;
if (IS_CANVAS_FILTER_SUPPORTED) {
ctx.filter = `blur(${RADIUS}px)`;
}
ctx.drawImage(img, -RADIUS * 2, -RADIUS * 2, canvas.width + RADIUS * 4, canvas.height + RADIUS * 4);
if (!IS_CANVAS_FILTER_SUPPORTED) {
fastBlur(ctx, 0, 0, canvas.width, canvas.height, RADIUS, ITERATIONS);
}
};
img.onload = () => {
if (withRaf) {
requestAnimationFrame(processBlur);
} else {
processBlur();
}
};
img.src = dataUri;
}, [canvasRef, dataUri, forceUpdate, isDisabled, withRaf]);
return canvasRef;
}

View File

@ -19,6 +19,12 @@
.thumbnail ~ .full-media, .media-loading {
position: absolute;
}
.thumbnail {
&.empty {
visibility: hidden;
}
}
}
.animated-close-icon {

View File

@ -47,7 +47,6 @@ export const IS_SERVICE_WORKER_SUPPORTED = 'serviceWorker' in navigator;
export const IS_PROGRESSIVE_SUPPORTED = IS_SERVICE_WORKER_SUPPORTED;
export const IS_STREAMING_SUPPORTED = 'MediaSource' in window;
export const IS_OPUS_SUPPORTED = Boolean((new Audio()).canPlayType('audio/ogg; codecs=opus'));
export const IS_CANVAS_FILTER_SUPPORTED = 'filter' in (document.createElement('canvas').getContext('2d') || {});
export const DPR = window.devicePixelRatio || 1;