Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| from PIL import Image, ImageCms | |
| import pillow_heif # This is essential! It registers HEIF/HEIC/AVIF support | |
| import io | |
| import tempfile | |
| # Explicitly register openers from pillow-heif to ensure PIL can see them | |
| pillow_heif.register_heif_opener() | |
| def convert_to_png(input_image, color_mode, color_profile, compress_setting): | |
| """ | |
| Converts an input PIL image to PNG format with specified options. | |
| """ | |
| if input_image is None: | |
| raise gr.Error("Please upload an image first.") | |
| # 1. Handle Compression Setting | |
| # PNG compression is always lossless. This just controls file size vs. time. | |
| compress_levels = { | |
| "Fast (Large File)": 1, | |
| "Balanced (Recommended)": 6, | |
| "Best (Small File, Slow)": 9, | |
| "None (No Compression)": 0 | |
| } | |
| compress_level = compress_levels.get(compress_setting, 6) | |
| # 2. Handle Color Mode Conversion | |
| # We work on a copy to avoid changing the original input_image object | |
| image_copy = input_image.copy() | |
| if color_mode != "Auto (Keep Original)": | |
| try: | |
| image_copy = image_copy.convert(color_mode) | |
| except Exception as e: | |
| print(f"Warning: Could not convert to {color_mode}: {e}. Proceeding with original mode.") | |
| # 3. Handle Color Profile Conversion | |
| original_profile = image_copy.info.get("icc_profile") | |
| final_icc_profile = original_profile | |
| if color_profile == "Convert to sRGB": | |
| try: | |
| # Define standard sRGB profile | |
| srgb_profile = ImageCms.createProfile("sRGB") | |
| # Get input profile | |
| if original_profile: | |
| in_profile_bytes = io.BytesIO(original_profile) | |
| in_prof = ImageCms.ImageCmsProfile(in_profile_bytes) | |
| else: | |
| # If no profile, assume sRGB. | |
| in_prof = ImageCms.createProfile("sRGB") | |
| print("Warning: No input profile found, assuming sRGB for conversion.") | |
| # Ensure mode is compatible with ICC transform (e.g., not 'P' or 'L') | |
| if image_copy.mode not in ('RGB', 'RGBA'): | |
| image_copy = image_copy.convert('RGB') | |
| print("Converted image to RGB for profile transform.") | |
| # Build and apply the transform | |
| transform = ImageCms.buildTransform( | |
| in_prof, | |
| srgb_profile, | |
| image_copy.mode, # Input mode | |
| image_copy.mode # Output mode (keep it) | |
| ) | |
| image_copy = ImageCms.applyTransform(image_copy, transform) | |
| # Set the new profile to be saved with the PNG | |
| final_icc_profile = srgb_profile.tobytes() | |
| except Exception as e: | |
| # If transform fails, just print a warning and keep the original profile | |
| print(f"Error during color profile transform: {e}. Profile will be kept as original.") | |
| final_icc_profile = original_profile # Revert to original | |
| # 4. Prepare save parameters for PNG | |
| save_params = { | |
| "format": "PNG", | |
| "compress_level": compress_level | |
| } | |
| if final_icc_profile: | |
| save_params["icc_profile"] = final_icc_profile | |
| # 5. Save to a temporary file | |
| # We must return a file *path* for the gr.File component | |
| with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as temp_file: | |
| output_filepath = temp_file.name | |
| image_copy.save(output_filepath, **save_params) | |
| # Return the path to the saved file | |
| return output_filepath | |
| # --- Gradio UI --- | |
| # Description for the user | |
| title = "🖼️ Any-to-PNG Image Converter" | |
| description = """ | |
| Upload any image to convert it to a high-quality PNG. | |
| This tool uses `pillow-heif` to support modern formats like **HEIC, HEIF, and AVIF**, in addition to standards like **JPG, BMP, TIFF, and WEBP**. | |
| """ | |
| # Note on the color profiles | |
| profile_info = """ | |
| **A Note on Color Profiles (P3, Adobe RGB, RGB):** | |
| You requested P3 and Adobe RGB. These are wide-gamut profiles. | |
| * **"Keep Original Profile"** is the true "lossless" mode. If your image has a P3 profile (e.g., from an iPhone), this will preserve it. | |
| * **"Convert to sRGB"** is the "RGB" option you requested. It ensures the image is compatible with all browsers and viewers, as sRGB is the web standard. | |
| """ | |
| supported_formats = "Supported inputs: `JPG`, `JPEG`, `BMP`, `TIFF`, `WEBP`, `HEIF`, `HEIC`, `AVIF`, and more." | |
| with gr.Blocks(theme=gr.themes.Soft()) as demo: | |
| gr.Markdown(f"# {title}") | |
| gr.Markdown(description) | |
| gr.Markdown(supported_formats) | |
| with gr.Row(variant="panel"): | |
| with gr.Column(scale=1): | |
| # Input Image | |
| input_image = gr.Image( | |
| type="pil", | |
| label="Upload Image", | |
| ) | |
| gr.Markdown("### ⚙️ Conversion Options") | |
| compress_setting = gr.Dropdown( | |
| ["Fast (Large File)", "Balanced (Recommended)", "Best (Small File, Slow)", "None (No Compression)"], | |
| label="PNG Compression Level", | |
| value="Balanced (Recommended)", | |
| info="PNG is always lossless. This just controls file size vs. speed." | |
| ) | |
| color_mode = gr.Radio( | |
| ["Auto (Keep Original)", "RGB", "RGBA", "L (Grayscale)"], | |
| label="Color Mode", | |
| value="Auto (Keep Original)", | |
| info="Force the output image to a specific color mode." | |
| ) | |
| color_profile = gr.Radio( | |
| ["Keep Original Profile", "Convert to sRGB"], | |
| label="Color Profile", | |
| value="Keep Original Profile" | |
| ) | |
| gr.Markdown(profile_info) | |
| # Submit Button | |
| convert_btn = gr.Button("Convert to PNG", variant="primary") | |
| with gr.Column(scale=1): | |
| # Output File | |
| gr.Markdown("### 🔽 Download Result") | |
| output_file = gr.File(label="Download PNG") | |
| # Connect components | |
| convert_btn.click( | |
| fn=convert_to_png, | |
| inputs=[input_image, color_mode, color_profile, compress_setting], | |
| outputs=output_file | |
| ) | |
| # Launch the app | |
| if __name__ == "__main__": | |
| demo.launch() |