Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor:简化头像计算,新增头像最大尺寸参数,避免头像裁剪出来太大 || refactor: Simplify the avatar calculation and add a new avatar. Maximum size parameter to prevent the avatar from being cropped too large #199

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions client/web/src/components/ImagePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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; // 禁用选择
}
Expand Down Expand Up @@ -43,6 +44,7 @@ export const ImagePicker: React.FC<ImagePickerProps> = React.memo((props) => {
<ImageCropperModal
imageUrl={reader.result.toString()}
aspect={props.aspect}
maxSize={props.maxSize}
onConfirm={(croppedImageBlobUrl) => {
closeModal(key);
updateAvatar(croppedImageBlobUrl);
Expand Down
11 changes: 10 additions & 1 deletion client/web/src/components/ImageUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ImagePicker } from './ImagePicker';
interface ImageUploaderProps extends PropsWithChildren {
circle?: boolean;
aspect?: number;
maxSize?: number;
className?: string;
onUploadSuccess: (fileInfo: UploadFileResult) => void;
}
Expand Down Expand Up @@ -39,6 +40,7 @@ export const ImageUploader: React.FC<ImageUploaderProps> = React.memo(
'rounded-full': props.circle,
})}
aspect={aspect}
maxSize={props.maxSize}
disabled={loading}
onChange={handlePickImage}
>
Expand All @@ -58,7 +60,14 @@ ImageUploader.displayName = 'ImageUploader';

export const AvatarUploader: React.FC<ImageUploaderProps> = React.memo(
(props) => {
return <ImageUploader aspect={1} circle={true} {...props}></ImageUploader>;
return (
<ImageUploader
aspect={1}
maxSize={256}
circle={true}
{...props}
></ImageUploader>
);
}
);
AvatarUploader.displayName = 'AvatarUploader';
50 changes: 26 additions & 24 deletions client/web/src/components/modals/ImageCropper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,22 @@ 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<Area>({ width: 0, height: 0, x: 0, y: 0 });

const handleConfirm = async () => {
const blobUrl = await getCroppedImg(
await createImage(props.imageUrl),
area
area,
0,
'newFile.jpeg',
maxSize
);
props.onConfirm(blobUrl);
};
Expand Down Expand Up @@ -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<string> {
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
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is design for common image cropper not only for avatar. for example: group background.

maybe you can check component in client/web/src/components/ImageUploader.tsx#AvatarUploader

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, understanding

}

Expand Down