2022-12-10 12:14:48 +03:00
|
|
|
import './avatar.css';
|
|
|
|
|
2023-03-13 05:10:21 +03:00
|
|
|
import { useRef } from 'preact/hooks';
|
|
|
|
|
2023-10-21 07:21:05 +03:00
|
|
|
import mem from '../utils/mem';
|
|
|
|
|
2022-12-10 12:14:48 +03:00
|
|
|
const SIZES = {
|
|
|
|
s: 16,
|
|
|
|
m: 20,
|
|
|
|
l: 24,
|
|
|
|
xl: 32,
|
|
|
|
xxl: 50,
|
2022-12-17 19:38:19 +03:00
|
|
|
xxxl: 64,
|
2022-12-10 12:14:48 +03:00
|
|
|
};
|
|
|
|
|
2023-03-13 09:24:53 +03:00
|
|
|
const alphaCache = {};
|
|
|
|
|
2023-06-14 06:15:05 +03:00
|
|
|
const canvas = window.OffscreenCanvas
|
|
|
|
? new OffscreenCanvas(1, 1)
|
|
|
|
: document.createElement('canvas');
|
2023-09-09 09:26:08 +03:00
|
|
|
const ctx = canvas.getContext('2d', {
|
|
|
|
willReadFrequently: true,
|
|
|
|
});
|
2023-06-14 06:15:05 +03:00
|
|
|
|
2023-04-10 19:26:43 +03:00
|
|
|
function Avatar({ url, size, alt = '', squircle, ...props }) {
|
2022-12-10 12:14:48 +03:00
|
|
|
size = SIZES[size] || size || SIZES.m;
|
2023-03-13 05:10:21 +03:00
|
|
|
const avatarRef = useRef();
|
2023-03-21 10:52:26 +03:00
|
|
|
const isMissing = /missing\.png$/.test(url);
|
2022-12-10 12:14:48 +03:00
|
|
|
return (
|
|
|
|
<span
|
2023-03-13 05:10:21 +03:00
|
|
|
ref={avatarRef}
|
2023-04-10 19:26:43 +03:00
|
|
|
class={`avatar ${squircle ? 'squircle' : ''} ${
|
|
|
|
alphaCache[url] ? 'has-alpha' : ''
|
|
|
|
}`}
|
2022-12-10 12:14:48 +03:00
|
|
|
style={{
|
|
|
|
width: size,
|
|
|
|
height: size,
|
|
|
|
}}
|
2022-12-14 17:46:50 +03:00
|
|
|
title={alt}
|
2023-01-21 19:37:46 +03:00
|
|
|
{...props}
|
2022-12-10 12:14:48 +03:00
|
|
|
>
|
|
|
|
{!!url && (
|
2023-03-13 05:10:21 +03:00
|
|
|
<img
|
|
|
|
src={url}
|
|
|
|
width={size}
|
|
|
|
height={size}
|
|
|
|
alt={alt}
|
|
|
|
loading="lazy"
|
2023-06-14 15:31:02 +03:00
|
|
|
decoding="async"
|
2023-03-21 10:52:26 +03:00
|
|
|
crossOrigin={
|
|
|
|
alphaCache[url] === undefined && !isMissing
|
|
|
|
? 'anonymous'
|
|
|
|
: undefined
|
|
|
|
}
|
2023-03-13 09:24:53 +03:00
|
|
|
onError={(e) => {
|
2023-03-13 14:25:00 +03:00
|
|
|
if (e.target.crossOrigin) {
|
|
|
|
e.target.crossOrigin = null;
|
|
|
|
e.target.src = url;
|
|
|
|
}
|
2023-03-13 09:24:53 +03:00
|
|
|
}}
|
2023-03-13 05:10:21 +03:00
|
|
|
onLoad={(e) => {
|
2023-03-13 11:22:41 +03:00
|
|
|
if (avatarRef.current) avatarRef.current.dataset.loaded = true;
|
2023-03-19 08:38:40 +03:00
|
|
|
if (alphaCache[url] !== undefined) return;
|
2023-03-21 10:52:26 +03:00
|
|
|
if (isMissing) return;
|
2023-12-28 05:50:54 +03:00
|
|
|
queueMicrotask(() => {
|
|
|
|
try {
|
|
|
|
// Check if image has alpha channel
|
|
|
|
const { width, height } = e.target;
|
|
|
|
if (canvas.width !== width) canvas.width = width;
|
|
|
|
if (canvas.height !== height) canvas.height = height;
|
|
|
|
ctx.drawImage(e.target, 0, 0);
|
|
|
|
const allPixels = ctx.getImageData(0, 0, width, height);
|
|
|
|
// At least 10% of pixels have alpha <= 128
|
|
|
|
const hasAlpha =
|
|
|
|
allPixels.data.filter(
|
|
|
|
(pixel, i) => i % 4 === 3 && pixel <= 128,
|
|
|
|
).length /
|
|
|
|
(allPixels.data.length / 4) >
|
|
|
|
0.1;
|
|
|
|
if (hasAlpha) {
|
|
|
|
// console.log('hasAlpha', hasAlpha, allPixels.data);
|
|
|
|
avatarRef.current.classList.add('has-alpha');
|
|
|
|
}
|
|
|
|
alphaCache[url] = hasAlpha;
|
|
|
|
ctx.clearRect(0, 0, width, height);
|
|
|
|
} catch (e) {
|
|
|
|
// Silent fail
|
|
|
|
alphaCache[url] = false;
|
2023-03-13 09:24:53 +03:00
|
|
|
}
|
2023-12-28 05:50:54 +03:00
|
|
|
});
|
2023-03-13 05:10:21 +03:00
|
|
|
}}
|
|
|
|
/>
|
2022-12-10 12:14:48 +03:00
|
|
|
)}
|
|
|
|
</span>
|
|
|
|
);
|
2022-12-16 08:27:04 +03:00
|
|
|
}
|
|
|
|
|
2023-10-21 07:21:05 +03:00
|
|
|
export default mem(Avatar);
|