MichaelRKessler commited on
Commit
a40762d
·
verified ·
1 Parent(s): 9f718e2

Sync from GitHub via hub-sync

Browse files
Files changed (10) hide show
  1. .python-version +1 -0
  2. .vscode/settings.json +4 -0
  3. Agents.md +15 -0
  4. HelloWorld.py +2 -0
  5. README.md +49 -9
  6. app.py +238 -0
  7. main.py +6 -0
  8. pyproject.toml +10 -0
  9. requirements.txt +2 -0
  10. uv.lock +0 -0
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.12
.vscode/settings.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "python-envs.defaultEnvManager": "ms-python.python:conda",
3
+ "python-envs.defaultPackageManager": "ms-python.python:conda"
4
+ }
Agents.md ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python Environment
2
+
3
+ Always use `uv` to run Python scripts and manage dependencies — never use `pip` or `python` directly.
4
+
5
+ - Run scripts with `uv run python script.py` instead of `python script.py`
6
+ - Install packages with `uv add package-name` instead of `pip install`
7
+ - To run one-off commands: `uv run <command>`
8
+ - The project uses `uv` for virtual environment management; do not create venvs manually with `python -m venv`
9
+
10
+ ## Common Commands
11
+
12
+ - `uv run python script.py` — run a script
13
+ - `uv add <package>` — add a dependency
14
+ - `uv sync` — install all dependencies from lockfile
15
+ - `uv run pytest` — run tests
HelloWorld.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ print("Hello World! This is a simple Python program!")
2
+ import pandas as pd
README.md CHANGED
@@ -1,15 +1,55 @@
1
  ---
2
- title: ImageRotate
3
- emoji:
4
- colorFrom: red
5
- colorTo: indigo
6
  sdk: gradio
7
- sdk_version: 6.16.0
8
- python_version: '3.13'
9
  app_file: app.py
10
  pinned: false
11
- license: mit
12
- short_description: Rotate and image file or from clipboard
13
  ---
14
 
15
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Image Rotator Pro
3
+ emoji: 🔄
4
+ colorFrom: indigo
5
+ colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 6.14.0
 
8
  app_file: app.py
9
  pinned: false
 
 
10
  ---
11
 
12
+ # ImageRotate
13
+
14
+
15
+ This repository contains experimental scripts and applications.
16
+
17
+ ## 🔄 Image Rotator Pro
18
+
19
+ A beautiful, premium Gradio application for rotating images by standard steps or custom angles. It supports uploading files, drag-and-drop, and pasting images directly from the clipboard.
20
+
21
+ ### Features
22
+ - **Flexible Inputs**: Drag and drop, file browser, or paste directly from clipboard (Ctrl+V).
23
+ - **One-Click Presets**: Quick actions to rotate `90° CCW`, `90° CW`, or `180°`, plus a quick `Reset` button.
24
+ - **Custom Precision**: Interactive slider to rotate by any angle between `-360°` and `360°`.
25
+ - **Advanced Canvas Settings**:
26
+ - **Expand Canvas**: Option to expand the output canvas to fit the rotated image or clip it to the original size.
27
+ - **Custom Background Fills**: Select a transparent background (transparency layer, requires PNG export), black, white, or input a custom hex color code.
28
+ - **EXIF Correction**: Automatically respects camera orientation tags so images are rendered correctly.
29
+ - **Modern UI**: Designed using custom CSS glassmorphism, gradient banners, and a clean, responsive layout.
30
+
31
+ ---
32
+
33
+ ### How to Run the App
34
+
35
+ This project uses `uv` for package and environment management. Follow these guidelines to run the application:
36
+
37
+ #### 1. Setup Environment & Install Dependencies
38
+ First, ensure that `uv` is installed, then synchronize the environment.
39
+ ```bash
40
+ # Sync dependencies from pyproject.toml
41
+ uv sync
42
+ ```
43
+ *Note: If you are setting up the project from scratch, you can also install the dependencies manually using:*
44
+ ```bash
45
+ uv add gradio pillow
46
+ ```
47
+
48
+ #### 2. Launch the Application
49
+ Run the Gradio server using `uv run`. We recommend using `-u` (unbuffered) so logs flush to console immediately:
50
+ ```bash
51
+ uv run python -u app.py
52
+ ```
53
+
54
+ Once running, open your web browser and navigate to:
55
+ 👉 **[http://127.0.0.1:7860](http://127.0.0.1:7860)**
app.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from PIL import Image, ImageOps
3
+
4
+ def process_rotation(image, angle, expand, fill_type, custom_color):
5
+ if image is None:
6
+ return None
7
+
8
+ # Preprocess image to respect EXIF orientation metadata
9
+ try:
10
+ image = ImageOps.exif_transpose(image)
11
+ except Exception as e:
12
+ print(f"EXIF transpose failed: {e}")
13
+
14
+ # Map fill type to color
15
+ if fill_type == "Transparent":
16
+ if image.mode != "RGBA":
17
+ image = image.convert("RGBA")
18
+ fill = (0, 0, 0, 0)
19
+ elif fill_type == "Black":
20
+ if image.mode == "RGBA":
21
+ fill = (0, 0, 0, 255)
22
+ else:
23
+ fill = (0, 0, 0)
24
+ elif fill_type == "White":
25
+ if image.mode == "RGBA":
26
+ fill = (255, 255, 255, 255)
27
+ else:
28
+ fill = (255, 255, 255)
29
+ elif fill_type == "Custom Color":
30
+ try:
31
+ hex_val = custom_color.lstrip('#')
32
+ rgb = tuple(int(hex_val[i:i+2], 16) for i in (0, 2, 4))
33
+ if image.mode == "RGBA":
34
+ fill = rgb + (255,)
35
+ else:
36
+ fill = rgb
37
+ except Exception as e:
38
+ print(f"Color parsing failed: {e}. Defaulting to transparent/black.")
39
+ fill = (0, 0, 0, 0) if image.mode == "RGBA" else (0, 0, 0)
40
+ else:
41
+ fill = (0, 0, 0, 0) if image.mode == "RGBA" else (0, 0, 0)
42
+
43
+ # PIL rotate: angle is clockwise, PIL rotates counter-clockwise
44
+ # So we pass -angle
45
+ rotated_img = image.rotate(-angle, expand=expand, fillcolor=fill, resample=Image.Resampling.BICUBIC)
46
+ return rotated_img
47
+
48
+ def toggle_color_picker(fill_type):
49
+ if fill_type == "Custom Color":
50
+ return gr.update(visible=True)
51
+ else:
52
+ return gr.update(visible=False)
53
+
54
+ def handle_image_upload(image, expand, fill_type, custom_color):
55
+ rotated = process_rotation(image, 0, expand, fill_type, custom_color)
56
+ return 0, rotated
57
+
58
+ # Custom CSS for premium styling
59
+ custom_css = """
60
+ .header-banner {
61
+ background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 50%, #db2777 100%);
62
+ padding: 2.5rem 2rem;
63
+ border-radius: 16px;
64
+ text-align: center;
65
+ color: white;
66
+ margin-bottom: 2rem;
67
+ box-shadow: 0 10px 30px -5px rgba(79, 70, 229, 0.3);
68
+ }
69
+ .header-banner h1 {
70
+ font-size: 2.5rem;
71
+ font-weight: 800;
72
+ margin: 0;
73
+ color: white !important;
74
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
75
+ letter-spacing: -0.025em;
76
+ }
77
+ .header-banner p {
78
+ font-size: 1.1rem;
79
+ margin-top: 0.75rem;
80
+ margin-bottom: 0;
81
+ opacity: 0.9;
82
+ font-weight: 400;
83
+ }
84
+ .rotate-btn {
85
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important;
86
+ font-weight: 600 !important;
87
+ cursor: pointer;
88
+ }
89
+ .rotate-btn:hover {
90
+ transform: translateY(-2px);
91
+ box-shadow: 0 4px 12px rgba(79, 70, 229, 0.2) !important;
92
+ }
93
+ .rotate-btn:active {
94
+ transform: translateY(0);
95
+ }
96
+ .control-section {
97
+ padding: 1rem;
98
+ border-radius: 12px;
99
+ background: rgba(255, 255, 255, 0.02);
100
+ border: 1px solid rgba(255, 255, 255, 0.05);
101
+ }
102
+ """
103
+
104
+ theme = gr.themes.Soft(
105
+ primary_hue="indigo",
106
+ secondary_hue="violet",
107
+ neutral_hue="slate",
108
+ font=[gr.themes.GoogleFont("Outfit"), "sans-serif"],
109
+ )
110
+
111
+ with gr.Blocks(title="Image Rotator Pro") as demo:
112
+ gr.HTML(
113
+ value="""
114
+ <div class="header-banner">
115
+ <h1>🔄 Image Rotator Pro</h1>
116
+ <p>Rotate your images with precision. Paste directly from the clipboard, drop files, and choose any angle.</p>
117
+ </div>
118
+ """
119
+ )
120
+
121
+ with gr.Row():
122
+ with gr.Column(scale=1):
123
+ gr.Markdown("### 📂 Input Source")
124
+ input_image = gr.Image(
125
+ type="pil",
126
+ sources=["upload", "clipboard"],
127
+ label="Drag & Drop, Browse, or Paste Image (Ctrl+V)"
128
+ )
129
+
130
+ with gr.Group():
131
+ gr.Markdown("### ⚙️ Quick Rotations")
132
+ with gr.Row():
133
+ btn_ccw_90 = gr.Button("↺ 90° CCW", elem_classes=["rotate-btn"])
134
+ btn_cw_90 = gr.Button("↻ 90° CW", elem_classes=["rotate-btn"])
135
+ with gr.Row():
136
+ btn_rotate_180 = gr.Button("⇅ 180°", elem_classes=["rotate-btn"])
137
+ btn_reset = gr.Button("🗑️ Reset", variant="secondary", elem_classes=["rotate-btn"])
138
+
139
+ with gr.Column(scale=1):
140
+ gr.Markdown("### 🎛️ Precision & Canvas Controls")
141
+ angle_slider = gr.Slider(
142
+ minimum=-360,
143
+ maximum=360,
144
+ value=0,
145
+ step=1,
146
+ label="Custom Angle (Degrees)"
147
+ )
148
+
149
+ with gr.Accordion("Advanced Canvas Settings", open=True):
150
+ expand_canvas = gr.Checkbox(
151
+ value=True,
152
+ label="Expand canvas to fit rotated image",
153
+ info="If disabled, the image is cropped to its original dimensions."
154
+ )
155
+
156
+ fill_type = gr.Radio(
157
+ choices=["Transparent", "Black", "White", "Custom Color"],
158
+ value="Transparent",
159
+ label="Background Fill Color",
160
+ info="Used for areas revealed by rotation (transparency requires PNG output)"
161
+ )
162
+
163
+ custom_color = gr.ColorPicker(
164
+ value="#4f46e5",
165
+ label="Select Custom Background Color",
166
+ visible=False
167
+ )
168
+
169
+ gr.Markdown("### 🖼️ Rotated Preview")
170
+ output_image = gr.Image(
171
+ type="pil",
172
+ label="Rotated Output (Preview & Download)",
173
+ interactive=False
174
+ )
175
+
176
+ # Event wiring
177
+ # Toggling color picker visibility
178
+ fill_type.change(
179
+ fn=toggle_color_picker,
180
+ inputs=[fill_type],
181
+ outputs=[custom_color]
182
+ )
183
+
184
+ # Quick Action Buttons change the slider
185
+ btn_ccw_90.click(
186
+ fn=lambda val: (val - 90) % 360,
187
+ inputs=[angle_slider],
188
+ outputs=[angle_slider]
189
+ )
190
+ btn_cw_90.click(
191
+ fn=lambda val: (val + 90) % 360,
192
+ inputs=[angle_slider],
193
+ outputs=[angle_slider]
194
+ )
195
+ btn_rotate_180.click(
196
+ fn=lambda val: (val + 180) % 360,
197
+ inputs=[angle_slider],
198
+ outputs=[angle_slider]
199
+ )
200
+ btn_reset.click(
201
+ fn=lambda: 0,
202
+ inputs=[],
203
+ outputs=[angle_slider]
204
+ )
205
+
206
+ # Uploading/changing the input image resets the angle slider to 0 and updates the preview
207
+ input_image.change(
208
+ fn=handle_image_upload,
209
+ inputs=[input_image, expand_canvas, fill_type, custom_color],
210
+ outputs=[angle_slider, output_image]
211
+ )
212
+
213
+ # Whenever rotation parameters change, compute new preview
214
+ rotation_inputs = [input_image, angle_slider, expand_canvas, fill_type, custom_color]
215
+
216
+ angle_slider.change(
217
+ fn=process_rotation,
218
+ inputs=rotation_inputs,
219
+ outputs=[output_image]
220
+ )
221
+ expand_canvas.change(
222
+ fn=process_rotation,
223
+ inputs=rotation_inputs,
224
+ outputs=[output_image]
225
+ )
226
+ fill_type.change(
227
+ fn=process_rotation,
228
+ inputs=rotation_inputs,
229
+ outputs=[output_image]
230
+ )
231
+ custom_color.change(
232
+ fn=process_rotation,
233
+ inputs=rotation_inputs,
234
+ outputs=[output_image]
235
+ )
236
+
237
+ if __name__ == "__main__":
238
+ demo.launch(theme=theme, css=custom_css)
main.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ def main():
2
+ print("Hello from imagerotate!")
3
+
4
+
5
+ if __name__ == "__main__":
6
+ main()
pyproject.toml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "imagerotate"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "gradio>=6.14.0",
9
+ "pillow>=12.2.0",
10
+ ]
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio>=6.14.0
2
+ pillow>=12.2.0
uv.lock ADDED
The diff for this file is too large to render. See raw diff