图像转阴影
blurPx
px
原理
先利用 canvas 获取到图片每一个点的颜色信息, 利用 dom 的box-shadow可以放置任意个阴影, 计算偏移后放置每一个像素点。
Code
import React, { useState, useEffect, useRef, useLayoutEffect } from 'react';
import { Upload, Button, Space, message, InputNumber } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import { isServer } from '@site/src/utils';
function getImageData(image: HTMLImageElement) {
    if (isServer) return [];
    const canvas = document.createElement('canvas');
    canvas.width = image.width;
    canvas.height = image.height;
    const ctx = canvas.getContext('2d');
    ctx.drawImage(image, 0, 0, image.width, image.height);
    const imageData = ctx.getImageData(0, 0, image.width, image.height).data;
    return imageData;
}
export function Image2Shadow() {
    const [file, setFile] = useState<File | null>(null);
    const shadowRef = useRef<HTMLDivElement>();
    const [options, setOptions] = useState<{
        imageData: number[];
        image: HTMLImageElement;
        blurPx: number;
    }>({
        imageData: [],
        image: isServer ? null : new Image(),
        blurPx: 0,
    });
    useEffect(() => {
        console.log('Input File', file);
        if (!file || !shadowRef.current) return;
        const url = URL.createObjectURL(file);
        const image = new Image();
        image.src = url;
        new Promise<void>((resolve, reject) => {
            image.onload = () => {
                console.log('image load success', image, image.width, image.height);
                message.success(
                    `image load success, width : ${image.width} height : ${image.height}`,
                );
                resolve();
            };
            image.onerror = e => {
                console.log('image load error', e);
                message.error(`image load error, ${e.toString()}`);
                reject();
            };
        }).then(() => {
            setOptions({
                image,
                imageData: Array.from(getImageData(image)),
                blurPx: 0,
            });
        });
    }, [file]);
    useLayoutEffect(() => {
        if (!shadowRef.current) return;
        console.log('options', options);
        const { image, imageData } = options;
        const dom: HTMLDivElement = shadowRef.current;
        dom.style.marginRight = image.width + 'px';
        dom.style.marginBottom = image.height + 'px';
        const shadowlist: string[] = [];
        for (let i = 0; i < imageData.length; i += 4) {
            const [r, g, b, a] = [
                imageData[i],
                imageData[i + 1],
                imageData[i + 2],
                imageData[i + 3],
            ];
            const [widthOffset, heightOffset] = [
                (i / 4) % image.width,
                Math.floor(i / 4 / image.width),
            ];
            shadowlist.push(
                `${widthOffset}px ${heightOffset}px  ${
                    options.blurPx
                }px 1px rgba(${r}, ${g}, ${b}, ${a / 255})`,
            );
        }
        dom.style.boxShadow = shadowlist.join(',');
    }, [options]);
    return (
        <Space direction="vertical">
            <Upload
                showUploadList={false}
                beforeUpload={file => {
                    setFile(file);
                    return false;
                }}
                maxCount={1}
                listType="picture"
            >
                <Button icon={<UploadOutlined />}>Select File</Button>
            </Upload>
            <InputNumber
                min={0}
                addonBefore="blurPx"
                addonAfter="px"
                value={options.blurPx}
                onChange={blurPx => {
                    setOptions({ ...options, blurPx });
                }}
            />
            <div ref={shadowRef} id="image2Shadow" style={{ width: 0, height: 0 }}></div>
        </Space>
    );
}