feat: rotate images

This commit is contained in:
trafficlunar 2026-03-29 13:16:25 +01:00
parent 6167da3703
commit 5ef104904a
2 changed files with 28 additions and 7 deletions

View file

@ -11,7 +11,7 @@ interface Props {
setImage: React.Dispatch<React.SetStateAction<string | undefined>>; setImage: React.Dispatch<React.SetStateAction<string | undefined>>;
} }
export default function CropPortrait({ isOpen, setIsOpen, image, setImage }: Props) { export default function ImageEditorPortrait({ isOpen, setIsOpen, image, setImage }: Props) {
const [isVisible, setIsVisible] = useState(false); const [isVisible, setIsVisible] = useState(false);
const [crop, setCrop] = useState<Crop>(); const [crop, setCrop] = useState<Crop>();
@ -38,9 +38,27 @@ export default function CropPortrait({ isOpen, setIsOpen, image, setImage }: Pro
ctx.drawImage(image, crop.x * scaleX, crop.y * scaleY, crop.width * scaleX, crop.height * scaleY, 0, 0, crop.width * scaleX, crop.height * scaleY); ctx.drawImage(image, crop.x * scaleX, crop.y * scaleY, crop.width * scaleX, crop.height * scaleY, 0, 0, crop.width * scaleX, crop.height * scaleY);
setImage(canvas.toDataURL()); setImage(canvas.toDataURL());
close(); setCrop(undefined);
}, [crop, setImage]); }, [crop, setImage]);
const rotate = () => {
if (!imageRef.current || !canvasRef.current) return;
const image = imageRef.current;
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
if (!ctx) return;
canvas.width = image.naturalHeight;
canvas.height = image.naturalWidth;
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(Math.PI / 2);
ctx.drawImage(image, -image.naturalWidth / 2, -image.naturalHeight / 2);
setImage(canvas.toDataURL());
};
const close = () => { const close = () => {
setIsVisible(false); setIsVisible(false);
setTimeout(() => { setTimeout(() => {
@ -68,7 +86,7 @@ export default function CropPortrait({ isOpen, setIsOpen, image, setImage }: Pro
}`} }`}
> >
<div className="flex justify-between items-center mb-2"> <div className="flex justify-between items-center mb-2">
<h2 className="text-xl font-bold">Crop Portrait</h2> <h2 className="text-xl font-bold">Edit Image</h2>
<button type="button" aria-label="Close" onClick={close} className="text-red-400 hover:text-red-500 text-2xl cursor-pointer"> <button type="button" aria-label="Close" onClick={close} className="text-red-400 hover:text-red-500 text-2xl cursor-pointer">
<Icon icon="material-symbols:close-rounded" /> <Icon icon="material-symbols:close-rounded" />
</button> </button>
@ -88,6 +106,9 @@ export default function CropPortrait({ isOpen, setIsOpen, image, setImage }: Pro
<button type="button" onClick={applyCrop} className="pill button"> <button type="button" onClick={applyCrop} className="pill button">
Crop Crop
</button> </button>
<button type="button" onClick={rotate} className="pill button">
Rotate
</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -5,7 +5,7 @@ import { FileWithPath } from "react-dropzone";
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import Dropzone from "../dropzone"; import Dropzone from "../dropzone";
import Camera from "./camera"; import Camera from "./camera";
import CropPortrait from "./crop-portrait"; import ImageEditorPortrait from "./image-editor";
interface Props { interface Props {
text: string; text: string;
@ -55,8 +55,8 @@ export default function SwitchFileUpload({ text, forceCrop, image, setImage }: P
Use your camera Use your camera
</button> </button>
<button type="button" aria-label="Crop image" onClick={() => setIsCropOpen(true)} className="pill button gap-2"> <button type="button" aria-label="Crop image" onClick={() => setIsCropOpen(true)} className="pill button gap-2">
<Icon icon="material-symbols:crop" fontSize={20} /> <Icon icon="mdi:image-edit" fontSize={20} />
Crop Image Edit Image
</button> </button>
<Camera <Camera
@ -67,7 +67,7 @@ export default function SwitchFileUpload({ text, forceCrop, image, setImage }: P
if (forceCrop) setIsCropOpen(true); if (forceCrop) setIsCropOpen(true);
}} }}
/> />
<CropPortrait isOpen={isCropOpen} setIsOpen={setIsCropOpen} image={image} setImage={setImage} /> <ImageEditorPortrait isOpen={isCropOpen} setIsOpen={setIsCropOpen} image={image} setImage={setImage} />
</div> </div>
); );
} }