| import contextlib |
| from io import BytesIO |
|
|
| import numpy as np |
| import requests |
| import streamlit as st |
| from PIL import Image, ImageEnhance, ImageOps |
| from rembg import remove |
| from streamlit_cropper import st_cropper |
|
|
| VERSION = "0.7.0" |
|
|
| st.set_page_config( |
| page_title="Image WorkDesk", |
| page_icon="🖼️", |
| menu_items={ |
| "About": f"Image WorkDesk v{VERSION} " |
| f"\nApp contact: [Siddhant Sadangi](mailto:siddhant.sadangi@gmail.com)", |
| "Report a Bug": "https://github.com/SiddhantSadangi/ImageWorkdesk/issues/new", |
| "Get help": None, |
| }, |
| layout="wide", |
| ) |
|
|
| |
| with open("sidebar.html", "r", encoding="UTF-8") as sidebar_file: |
| sidebar_html = sidebar_file.read().replace("{VERSION}", VERSION) |
|
|
| with st.sidebar: |
| with st.expander("Supported operations"): |
| st.info( |
| "* Upload image, take one with your camera, or load from a URL\n" |
| "* Crop\n" |
| "* Remove background\n" |
| "* Mirror\n" |
| "* Convert to grayscale or black and white\n" |
| "* Rotate\n" |
| "* Change brightness, saturation, contrast, sharpness\n" |
| "* Generate random image from supplied image\n" |
| "* Download results" |
| ) |
| st.components.v1.html(sidebar_html, height=750) |
|
|
| |
| st.title("🖼️ Welcome to Image WorkDesk!") |
|
|
|
|
| |
| def _reset(key: str) -> None: |
| if key == "all": |
| st.session_state["rotate_slider"] = 0 |
| st.session_state["brightness_slider"] = st.session_state[ |
| "saturation_slider" |
| ] = st.session_state["contrast_slider"] = 100 |
| st.session_state["bg"] = st.session_state["crop"] = st.session_state[ |
| "mirror" |
| ] = st.session_state["gray_bw"] = 0 |
| elif key == "rotate_slider": |
| st.session_state["rotate_slider"] = 0 |
| elif key == "checkboxes": |
| st.session_state["crop"] = st.session_state["mirror"] = st.session_state[ |
| "gray_bw" |
| ] = 0 |
| else: |
| st.session_state[key] = 100 |
|
|
|
|
| def _randomize() -> None: |
| st.session_state["mirror"] = np.random.choice([0, 1]) |
| st.session_state["rotate_slider"] = np.random.randint(0, 360) |
| st.session_state["brightness_slider"] = np.random.randint(0, 200) |
| st.session_state["saturation_slider"] = np.random.randint(0, 200) |
| st.session_state["contrast_slider"] = np.random.randint(0, 200) |
| st.session_state["sharpness_slider"] = np.random.randint(0, 200) |
|
|
|
|
| |
|
|
| option = st.radio( |
| label="Upload an image, take one with your camera, or load image from a URL", |
| options=( |
| "Upload an image ⬆️", |
| "Take a photo with my camera 📷", |
| "Load image from a URL 🌐", |
| ), |
| help="Uploaded images are deleted from the server when you\n* upload another image\n* clear the file uploader\n* close the browser tab", |
| ) |
|
|
| if option == "Take a photo with my camera 📷": |
| upload_img = st.camera_input( |
| label="Take a picture", |
| ) |
| mode = "camera" |
|
|
| elif option == "Upload an image ⬆️": |
| upload_img = st.file_uploader( |
| label="Upload an image", |
| type=["bmp", "jpg", "jpeg", "png", "svg"], |
| ) |
| mode = "upload" |
|
|
| elif option == "Load image from a URL 🌐": |
| url = st.text_input( |
| "Image URL", |
| key="url", |
| ) |
| mode = "url" |
|
|
| if url != "": |
| try: |
| response = requests.get(url) |
| upload_img = Image.open(BytesIO(response.content)) |
| except: |
| st.error("The URL does not seem to be valid.") |
|
|
| with contextlib.suppress(NameError): |
| if upload_img is not None: |
| pil_img = ( |
| upload_img.convert("RGB") |
| if mode == "url" |
| else Image.open(upload_img).convert("RGB") |
| ) |
| img_arr = np.asarray(pil_img) |
|
|
| |
| st.image(img_arr, use_column_width="auto", caption="Uploaded Image") |
| st.text( |
| f"Original width = {pil_img.size[0]}px and height = {pil_img.size[1]}px" |
| ) |
|
|
| st.caption("All changes are applied on top of the previous change.") |
|
|
| |
| st.text("Crop image ✂️") |
| cropped_img = st_cropper(Image.fromarray(img_arr), should_resize_image=True) |
| st.text( |
| f"Cropped width = {cropped_img.size[0]}px and height = {cropped_img.size[1]}px" |
| ) |
|
|
| with st.container(): |
| lcol, rcol = st.columns(2) |
| if lcol.checkbox( |
| label="Use cropped Image?", |
| help="Select to use the cropped image in further operations", |
| key="crop", |
| ): |
| image = cropped_img |
| else: |
| image = Image.fromarray(img_arr) |
|
|
| |
| if lcol.checkbox( |
| label="Remove background?", |
| help="Select to remove background from the image", |
| key="bg", |
| ): |
| image = remove(image) |
|
|
| |
| if lcol.checkbox( |
| label="Mirror image? 🪞", |
| help="Select to mirror the image", |
| key="mirror", |
| ): |
| image = ImageOps.mirror(image) |
|
|
| |
| flag = True |
|
|
| if lcol.checkbox( |
| "Convert to grayscale / black & white? 🔲", |
| key="gray_bw", |
| help="Select to convert image to grayscale or black and white", |
| ): |
| mode = "L" |
| if ( |
| lcol.radio( |
| label="Grayscale or B&W", |
| options=("Grayscale", "Black & White"), |
| ) |
| == "Grayscale" |
| ): |
| image = image.convert(mode) |
| else: |
| flag = False |
| lcol.warning( |
| "Some operations not available for black and white images." |
| ) |
| thresh = np.array(image).mean() |
| fn = lambda x: 255 if x > thresh else 0 |
| image = image.convert(mode).point(fn, mode="1") |
| else: |
| mode = "RGB" |
| rcol.image( |
| image, |
| use_column_width="auto", |
| ) |
|
|
| if lcol.button( |
| "↩️ Reset", |
| on_click=_reset, |
| use_container_width=True, |
| kwargs={"key": "checkboxes"}, |
| ): |
| lcol.success("Image reset to original!") |
|
|
| st.markdown("""---""") |
|
|
| |
| |
| with st.container(): |
| lcol, mcol, rcol = st.columns(3) |
|
|
| |
| if "rotate_slider" not in st.session_state: |
| st.session_state["rotate_slider"] = 0 |
| degrees = lcol.slider( |
| "Drag slider to rotate image clockwise 🔁", |
| min_value=0, |
| max_value=360, |
| value=st.session_state["rotate_slider"], |
| key="rotate_slider", |
| ) |
| rotated_img = image.rotate(360 - degrees) |
| lcol.image( |
| rotated_img, |
| use_column_width="auto", |
| caption=f"Rotated by {degrees} degrees clockwise", |
| ) |
| if lcol.button( |
| "↩️ Reset Rotation", |
| on_click=_reset, |
| use_container_width=True, |
| kwargs={"key": "rotate_slider"}, |
| ): |
| lcol.success("Rotation reset to original!") |
|
|
| if flag: |
| |
| if "brightness_slider" not in st.session_state: |
| st.session_state["brightness_slider"] = 100 |
| brightness_factor = mcol.slider( |
| "Drag slider to change brightness 💡", |
| min_value=0, |
| max_value=200, |
| value=st.session_state["brightness_slider"], |
| key="brightness_slider", |
| ) |
| brightness_img = np.asarray( |
| ImageEnhance.Brightness(rotated_img).enhance( |
| brightness_factor / 100 |
| ) |
| ) |
| mcol.image( |
| brightness_img, |
| use_column_width="auto", |
| caption=f"Brightness: {brightness_factor}%", |
| ) |
| if mcol.button( |
| "↩️ Reset Brightness", |
| on_click=_reset, |
| use_container_width=True, |
| kwargs={"key": "brightness_slider"}, |
| ): |
| mcol.success("Brightness reset to original!") |
|
|
| |
| if "saturation_slider" not in st.session_state: |
| st.session_state["saturation_slider"] = 100 |
| saturation_factor = rcol.slider( |
| "Drag slider to change saturation", |
| min_value=0, |
| max_value=200, |
| value=st.session_state["saturation_slider"], |
| key="saturation_slider", |
| ) |
| saturation_img = np.asarray( |
| ImageEnhance.Color(Image.fromarray(brightness_img)).enhance( |
| saturation_factor / 100 |
| ) |
| ) |
| rcol.image( |
| saturation_img, |
| use_column_width="auto", |
| caption=f"Saturation: {saturation_factor}%", |
| ) |
| if rcol.button( |
| "↩️ Reset Saturation", |
| on_click=_reset, |
| use_container_width=True, |
| kwargs={"key": "saturation_slider"}, |
| ): |
| rcol.success("Saturation reset to original!") |
|
|
| st.markdown("""---""") |
|
|
| |
| with st.container(): |
| lcol, mcol, rcol = st.columns(3) |
| |
| if "contrast_slider" not in st.session_state: |
| st.session_state["contrast_slider"] = 100 |
| contrast_factor = lcol.slider( |
| "Drag slider to change contrast", |
| min_value=0, |
| max_value=200, |
| value=st.session_state["contrast_slider"], |
| key="contrast_slider", |
| ) |
| contrast_img = np.asarray( |
| ImageEnhance.Contrast(Image.fromarray(saturation_img)).enhance( |
| contrast_factor / 100 |
| ) |
| ) |
| lcol.image( |
| contrast_img, |
| use_column_width="auto", |
| caption=f"Contrast: {contrast_factor}%", |
| ) |
| if lcol.button( |
| "↩️ Reset Contrast", |
| on_click=_reset, |
| use_container_width=True, |
| kwargs={"key": "contrast_slider"}, |
| ): |
| lcol.success("Contrast reset to original!") |
|
|
| |
| if "sharpness_slider" not in st.session_state: |
| st.session_state["sharpness_slider"] = 100 |
| sharpness_factor = mcol.slider( |
| "Drag slider to change sharpness", |
| min_value=0, |
| max_value=200, |
| value=st.session_state["sharpness_slider"], |
| key="sharpness_slider", |
| ) |
| sharpness_img = np.asarray( |
| ImageEnhance.Sharpness(Image.fromarray(contrast_img)).enhance( |
| sharpness_factor / 100 |
| ) |
| ) |
| mcol.image( |
| sharpness_img, |
| use_column_width="auto", |
| caption=f"Sharpness: {sharpness_factor}%", |
| ) |
| if mcol.button( |
| "↩️ Reset Sharpness", |
| on_click=_reset, |
| use_container_width=True, |
| kwargs={"key": "sharpness_slider"}, |
| ): |
| mcol.success("Sharpness reset to original!") |
|
|
| st.markdown("""---""") |
|
|
| |
| st.subheader("View Results") |
| lcol, rcol = st.columns(2) |
| lcol.image( |
| img_arr, |
| use_column_width="auto", |
| caption=f"Original Image ({pil_img.size[0]} x {pil_img.size[1]})", |
| ) |
|
|
| try: |
| final_image = sharpness_img |
| except NameError: |
| final_image = rotated_img |
|
|
| rcol.image( |
| final_image, |
| use_column_width="auto", |
| caption=f"Final Image ({final_image.shape[1]} x {final_image.shape[0]})" |
| if flag |
| else f"Final Image ({final_image.size[1]} x {final_image.size[0]})", |
| ) |
|
|
| if flag: |
| Image.fromarray(final_image).save("final_image.png") |
| else: |
| final_image.save("final_image.png") |
|
|
| col1, col2, col3 = st.columns(3) |
| if col1.button( |
| "↩️ Reset All", |
| on_click=_reset, |
| use_container_width=True, |
| kwargs={"key": "all"}, |
| ): |
| st.success(body="Image reset to original!", icon="↩️") |
| if col2.button( |
| "🔀 Surprise Me!", |
| on_click=_randomize, |
| use_container_width=True, |
| ): |
| st.success(body="Random image generated", icon="🔀") |
| with open("final_image.png", "rb") as file: |
| col3.download_button( |
| "💾Download final image", |
| data=file, |
| mime="image/png", |
| use_container_width=True, |
| ) |