Profile redesign: Allow animated and transparent avatars (#38663)
This commit is contained in:
parent
63a244fe1a
commit
6142c7b003
@ -64,15 +64,21 @@ export const ImageUploadModal: FC<
|
||||
const [imageBlob, setImageBlob] = useState<Blob | null>(null);
|
||||
|
||||
const handleFile = useCallback((file: File) => {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', () => {
|
||||
const result = reader.result;
|
||||
if (typeof result === 'string' && result.length > 0) {
|
||||
setImageSrc(result);
|
||||
setStep('crop');
|
||||
}
|
||||
});
|
||||
reader.readAsDataURL(file);
|
||||
try {
|
||||
parseImageFile(file, (result, isAnimated) => {
|
||||
if (isAnimated) {
|
||||
// If the image is animated, skip cropping and go straight to alt text.
|
||||
setImageBlob(file);
|
||||
setStep('alt');
|
||||
} else {
|
||||
setImageSrc(result);
|
||||
setStep('crop');
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Error with image parsing:', error);
|
||||
setStep('select');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleCrop = useCallback(
|
||||
@ -104,19 +110,20 @@ export const ImageUploadModal: FC<
|
||||
);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
switch (step) {
|
||||
case 'crop':
|
||||
setImageSrc(null);
|
||||
setStep('select');
|
||||
break;
|
||||
case 'alt':
|
||||
setImageBlob(null);
|
||||
if (step === 'crop') {
|
||||
setImageSrc(null);
|
||||
setStep('select');
|
||||
} else if (step === 'alt') {
|
||||
setImageBlob(null);
|
||||
if (imageSrc) {
|
||||
setStep('crop');
|
||||
break;
|
||||
default:
|
||||
onClose();
|
||||
} else {
|
||||
setStep('select');
|
||||
}
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
}, [onClose, step]);
|
||||
}, [imageSrc, onClose, step]);
|
||||
|
||||
return (
|
||||
<DialogModal
|
||||
@ -401,6 +408,54 @@ const StepAlt: FC<{
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses an image file and determines if it's an animated GIF and returns a data URI for cropping.
|
||||
* Based on https://gist.github.com/zakirt/faa4a58cec5a7505b10e3686a226f285.
|
||||
*/
|
||||
function parseImageFile(
|
||||
file: File,
|
||||
cb: (buffer: string, isAnimated: boolean) => void,
|
||||
): void {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const buffer = reader.result;
|
||||
if (!(buffer instanceof ArrayBuffer)) {
|
||||
throw new Error('Expected an ArrayBuffer');
|
||||
}
|
||||
|
||||
// Convert the ArrayBuffer to a base64 data URI.
|
||||
const bytes = new Uint8Array(buffer);
|
||||
const base64 = btoa(String.fromCharCode(...bytes));
|
||||
const dataUri = `data:${file.type};base64,${base64}`;
|
||||
|
||||
// If the file type is not a GIF, then it's not animated as we don't support animated WebP or PNG.
|
||||
if (file.type !== 'image/gif') {
|
||||
cb(dataUri, false);
|
||||
}
|
||||
|
||||
const view = new DataView(buffer, 10); // Start from the last 4 bytes of the Logical Screen Descriptor.
|
||||
let offset = 3;
|
||||
|
||||
// Check the first bit for the global color table flag.
|
||||
const globalColorTable = view.getInt8(0);
|
||||
if (globalColorTable & 0x08) {
|
||||
// Grab last three bits to calculate the global color table size, and skip it.
|
||||
offset += 3 * Math.pow(2, (globalColorTable & 0x7) + 1);
|
||||
}
|
||||
|
||||
// Check Graphics Control Extension and Graphics Control Label to access animated data.
|
||||
let delayTime = 0;
|
||||
if (view.getUint8(offset) & 0x21 && view.getUint8(offset + 1) & 0xf9) {
|
||||
// Skip to the delay time, which is stored in the next two bytes.
|
||||
delayTime = view.getUint16(offset + 4);
|
||||
}
|
||||
|
||||
// If there is a delay time, the GIF is animated.
|
||||
cb(dataUri, delayTime > 0);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
|
||||
async function calculateCroppedImage(
|
||||
imageSrc: string,
|
||||
crop: Area,
|
||||
@ -427,10 +482,7 @@ async function calculateCroppedImage(
|
||||
crop.height,
|
||||
);
|
||||
|
||||
return canvas.convertToBlob({
|
||||
quality: 0.7,
|
||||
type: 'image/jpeg',
|
||||
});
|
||||
return canvas.convertToBlob();
|
||||
}
|
||||
|
||||
function dataUriToImage(dataUri: string) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user