diff --git a/client/web/src/components/ImagePicker.tsx b/client/web/src/components/ImagePicker.tsx index 8a7f02964b3..bb42dec29da 100644 --- a/client/web/src/components/ImagePicker.tsx +++ b/client/web/src/components/ImagePicker.tsx @@ -10,6 +10,7 @@ interface ImagePickerProps extends PropsWithChildren { className?: string; imageUrl?: string; // 初始image url, 仅children为空时生效 aspect?: number; + maxSize?: number; onChange?: (blobUrl: string) => void; disabled?: boolean; // 禁用选择 } @@ -43,6 +44,7 @@ export const ImagePicker: React.FC = React.memo((props) => { { closeModal(key); updateAvatar(croppedImageBlobUrl); diff --git a/client/web/src/components/ImageUploader.tsx b/client/web/src/components/ImageUploader.tsx index a0c2effce9b..21390afe4c2 100644 --- a/client/web/src/components/ImageUploader.tsx +++ b/client/web/src/components/ImageUploader.tsx @@ -7,6 +7,7 @@ import { ImagePicker } from './ImagePicker'; interface ImageUploaderProps extends PropsWithChildren { circle?: boolean; aspect?: number; + maxSize?: number; className?: string; onUploadSuccess: (fileInfo: UploadFileResult) => void; } @@ -39,6 +40,7 @@ export const ImageUploader: React.FC = React.memo( 'rounded-full': props.circle, })} aspect={aspect} + maxSize={props.maxSize} disabled={loading} onChange={handlePickImage} > @@ -58,7 +60,14 @@ ImageUploader.displayName = 'ImageUploader'; export const AvatarUploader: React.FC = React.memo( (props) => { - return ; + return ( + + ); } ); AvatarUploader.displayName = 'AvatarUploader'; diff --git a/client/web/src/components/modals/ImageCropper.tsx b/client/web/src/components/modals/ImageCropper.tsx index dcea4e53dfd..a6630127979 100644 --- a/client/web/src/components/modals/ImageCropper.tsx +++ b/client/web/src/components/modals/ImageCropper.tsx @@ -11,9 +11,11 @@ import { ModalWrapper } from '../Modal'; export const ImageCropperModal: React.FC<{ imageUrl: string; aspect?: number; + maxSize?: number; onConfirm: (croppedImageBlobUrl: string) => void; }> = React.memo((props) => { const aspect = props.aspect ?? 1; + const maxSize = props.maxSize ?? Infinity; const [crop, setCrop] = useState({ x: 0, y: 0 }); const [zoom, setZoom] = useState(1); const [area, setArea] = useState({ width: 0, height: 0, x: 0, y: 0 }); @@ -21,7 +23,10 @@ export const ImageCropperModal: React.FC<{ const handleConfirm = async () => { const blobUrl = await getCroppedImg( await createImage(props.imageUrl), - area + area, + 0, + 'newFile.jpeg', + maxSize ); props.onConfirm(blobUrl); }; @@ -73,48 +78,45 @@ let fileUrlTemp: string | null = null; // 缓存裁剪后的图片url * @param crop 裁剪信息 * @param rotation 旋转角度 * @param fileName 文件名 + * @param maxSize 最大尺寸 * @returns 裁剪后的图片blob url */ function getCroppedImg( image: HTMLImageElement, crop: Area, rotation = 0, - fileName = 'newFile.jpeg' + fileName = 'newFile.jpeg', + maxSize = Infinity ): Promise { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!_isNil(ctx)) { - const maxSize = Math.max(image.width, image.height); - const safeArea = 2 * ((maxSize / 2) * Math.sqrt(2)); + // 计算最大尺寸 + const size = Math.min(Math.max(crop.width, crop.height), maxSize); - // set each dimensions to double largest dimension to allow for a safe area for the - // image to rotate in without being clipped by canvas context - canvas.width = safeArea; - canvas.height = safeArea; + // 计算缩放比例 + const scale = size / Math.max(crop.width, crop.height); + + canvas.width = scale * crop.width; + canvas.height = scale * crop.height; // translate canvas context to a central location on image to allow rotating around the center. - ctx.translate(safeArea / 2, safeArea / 2); + ctx.translate(canvas.width / 2, canvas.height / 2); ctx.rotate(getRadianAngle(rotation)); - ctx.translate(-safeArea / 2, -safeArea / 2); + ctx.translate(-canvas.width / 2, -canvas.height / 2); // draw rotated image and store data. ctx.drawImage( image, - safeArea / 2 - image.width * 0.5, - safeArea / 2 - image.height * 0.5 - ); - const data = ctx.getImageData(0, 0, safeArea, safeArea); - - // set canvas width to final desired crop size - this will clear existing context - canvas.width = crop.width; - canvas.height = crop.height; - - // paste generated rotate image with correct offsets for x,y crop values. - ctx.putImageData( - data, - Math.round(0 - safeArea / 2 + image.width * 0.5 - crop.x), - Math.round(0 - safeArea / 2 + image.height * 0.5 - crop.y) + 0, + 0, + image.width, + image.height, + -crop.x * scale, + -crop.y * scale, + image.width * scale, + image.height * scale ); }