from __future__ import annotations from typing import Literal import gradio as gr from PIL import Image RotationMode = Literal["Clockwise 90", "Counterclockwise 90", "Custom angle"] def rotate_image( image: Image.Image | None, rotation_mode: RotationMode, custom_angle: float | None, ) -> Image.Image | None: if image is None: return None if rotation_mode == "Clockwise 90": angle = -90 elif rotation_mode == "Counterclockwise 90": angle = 90 else: angle = -(custom_angle or 0) return image.rotate( angle, expand=True, resample=Image.Resampling.BICUBIC, fillcolor=(255, 255, 255, 0) if image.mode == "RGBA" else None, ) def show_custom_angle(rotation_mode: RotationMode): return gr.update(visible=rotation_mode == "Custom angle") def build_app() -> gr.Blocks: with gr.Blocks(title="PicRotate", theme=gr.themes.Soft()) as demo: gr.Markdown("# PicRotate") with gr.Row(): input_image = gr.Image( label="Image", sources=["upload", "clipboard"], type="pil", image_mode="RGBA", height=460, ) output_image = gr.Image( label="Rotated image", type="pil", image_mode="RGBA", height=460, ) with gr.Row(): rotation_mode = gr.Radio( choices=["Clockwise 90", "Counterclockwise 90", "Custom angle"], value="Clockwise 90", label="Rotation", ) custom_angle = gr.Number( value=45, label="Custom angle in degrees clockwise", precision=2, visible=False, ) rotate_button = gr.Button("Rotate", variant="primary") rotation_mode.change( fn=show_custom_angle, inputs=rotation_mode, outputs=custom_angle, ) rotate_button.click( fn=rotate_image, inputs=[input_image, rotation_mode, custom_angle], outputs=output_image, ) return demo if __name__ == "__main__": build_app().launch(server_name="0.0.0.0", server_port=7860)