0

我已经构建了一个 Slack 风格的头像图像上传和裁剪功能,但我无法将裁剪后的图像保存而不会损坏。原始文件上传,使用相同的端点和方法,工作得很好。它是从 Blob 手动创建的裁剪副本,总是损坏的。

步骤非常简单:

  1. 选择并上传图像文件
  2. 裁剪(react-image-crop)出现
  3. 选择区域,点击保存

在步骤 1 中,当文件输入发生变化时,文件被上传。文件被发送到流式端点,该端点将文件上传到 s3 存储桶。

<button type="button">Upload</button>
<input 
    type="file" 
    accept="image/*"
    onChange={onFileChange} 
/>

...

const onFileChange = async e => {
    e.preventDefault();
    let uploadedFile = e.target.files[0];
    await onSave(uploadedFile);
};

UploadFile var 是从输入控件返回的 File 对象。这很好用!还没有问题。

在此处输入图像描述

在第 3 步中,一旦您选择了图像的一个区域,react-image-crop 就会生成一个 Blob。

const getCroppedImage = (source, config) => {
    const canvas = document.createElement("canvas");
    const scaleX = source.naturalWidth / source.width;
    const scaleY = source.naturalHeight / source.height;
    canvas.width = config.width;
    canvas.height = config.height;
    const ctx = canvas.getContext("2d");

    ctx.drawImage(
        source,
        config.x * scaleX,
        config.y * scaleY,
        config.width * scaleX,
        config.height * scaleY,
        0,
        0,
        config.width,
        config.height
    );

    let mimeType = mime.lookup(userProfile.image_file.split(".").at(-1));

    return new Promise((resolve, reject) => {
        canvas.toBlob(blob => {
            if (!blob) {
                reject(new Error("Canvas is empty"));
                return;
            }
            resolve(blob); //***THIS BLOB...
        }, mimeType);
    });
};

这个 Blob 是有效的,因为我在保存之前在屏幕上显示了选定的区域:

const AvatarPreview = () => {
    if (activeAvatar) {
        return <ImageCropper imageToCrop={activeAvatar} onImageCropped={onImageCropped} />;
    }
    return <Icon icon="bi:person" />;
};

我将 react-image-crop 生成的 Blob 填充到 File 对象中,因为这是我的代码所期望的,就像步骤 1 一样。

const onImageCropped = croppedBlob => { //***IS PASSED IN HERE
    let croppedImg = URL.createObjectURL(croppedBlob);
    setActiveAvatar(croppedImg);
    const reader = new FileReader();
    reader.readAsDataURL(croppedBlob);
    reader.addEventListener("load", () => {
        let { result } = reader;
        let resultMimeType = result.split(";")[0].split(":")[1];
        let croppedFile = new File([result], userProfile.image_file, { type: resultMimeType }); //***NEW File object, from Blob
        setCroppedAvatar(croppedFile);
    }, false);
};

<button type="button" onClick={() => onSave(croppedAvatar)}>Save</button>

上面 FileReader 加载中的“结果”是 base64 图像数据:

'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAAHRyWFlaAAABZAAAABRnWFlaAAABeAAAABRiWFlaAAABjAAAABRyVFJDAAABoAAAAChnVFJDAAABoAAAAChiVFJDAAABoAAAACh3dHB0AAAByAAAABRjcHJ0AAAB3AAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAFgAAAAcAHMAUgBHAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…QCMFWeFZ9u3fPsWY3hh9uVHWllbSwtGASrIOQc1t6rt0OBqG6x7aVfdY859pnIwfDDhCfpwBUyaSus4VZO+susGGM60zrB1TuEjx5Ov76FBKUgNTltIQlP4UpQghKQMAAAADHApt6lv1x1HeHrzdny9KkJbDrpHLhQhKNx91EJBJ9SSaQkLS+4HTwvalKvmQMZ/PFa8hBCRntnirYUDqVYtEWlQ8JYJSrv8vnTy6W2h5/qBYmCgqbcmtgLSMg+YUyYD4aUUqTlJ4I+td+FLethRhai0sgoUOCg+hFPBwcxjruUie1k/VDOmenKkIcS2iJCzyeAAmvF7qjqFzVWurzfHV7jJluKBJ/hBwP6VL1u+KvqlD0lL0VeLt+2rZLjmO25MyqSwNuPK7nKh8l7vlioFejqU8tx453EnIPcmrWrvW5VFf6zK0GifTWs1n6TRoUrIaDZBSeDSVUzyMTY7gBIORR/EcXwTnFEozfc0xVwOYT/2Q=='

新的 File 对象对我来说似乎是合法的:

在此处输入图像描述

然后我再次通过端点发送图像,并上传。它出现在存储桶中,并且文件大小看起来是合法的(不是 0KB 像断流所表明的那样。)

但是,在下载并尝试打开文件时,它已损坏。我想我在某处遗漏了一个选项......一些可以使这项工作正常进行的小调整?File 对象是否没有正确形成?如何进一步解决此问题?

4

1 回答 1

0

修复就在其中。当然,只需一行代码即可完成所有工作。我所要做的就是改变 FileReader 加载结果的方式:

const onImageCropped = async croppedBlob => {
    let croppedImg = URL.createObjectURL(croppedBlob);
    let mimeType = croppedBlob.type;
    setActiveAvatar(croppedImg);
    let reader = new FileReader();
    reader.readAsArrayBuffer(croppedBlob); //Fixed the issue
    reader.addEventListener("load", () => {
        let { result } = reader;
        let croppedFile = new File([result], userProfile.image_file, { type: mimeType });
        setCroppedAvatar(croppedFile);
    }, false);
};

无论如何,它变得更加优雅。使用 FileReader 的 readAsArrayBuffer 代替工作。我猜在使用 readAsDataUrl 时缓冲区中有额外的信息,从而破坏了图像。

于 2022-02-03T12:44:07.523 回答