Spaces:
Sleeping
Sleeping
File size: 6,719 Bytes
7f7be37 eb818e4 7f7be37 9e1be05 7f7be37 01445c3 7f7be37 eb818e4 01445c3 eb818e4 01445c3 eb818e4 01445c3 eb818e4 01445c3 eb818e4 01445c3 eb818e4 7f7be37 01445c3 7f7be37 01445c3 7f7be37 01445c3 7f7be37 f49268a 9e1be05 ce906ec 9e1be05 7f7be37 9e1be05 ce906ec 7f7be37 ce906ec f49268a ce906ec 7f7be37 9e1be05 01445c3 9e1be05 f49268a 9e1be05 eb818e4 9e1be05 ce906ec 01445c3 ce906ec 7f7be37 9e1be05 ce906ec 9e1be05 eb818e4 7f7be37 ce906ec eb818e4 ce906ec 9e1be05 7f7be37 9e1be05 7f7be37 ce906ec 9e1be05 7f7be37 f49268a ce906ec f49268a 9e1be05 f49268a ce906ec 01445c3 7f7be37 eb818e4 01445c3 7f7be37 eb818e4 7f7be37 9e1be05 f49268a ce906ec 9e1be05 f49268a 9e1be05 f49268a ce906ec f49268a ce906ec f49268a ce906ec f49268a ce906ec f49268a 9e1be05 ce906ec 9e1be05 ce906ec 9e1be05 ce906ec | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | import os
import tempfile
from pathlib import Path
import gradio as gr
import cv2
import numpy as np
from PIL import Image, ImageCms
# =========================================================
# Color profile assets (Gradio Space safe)
# =========================================================
BASE_DIR = Path(__file__).resolve().parent
ICC_DIR = BASE_DIR / "icc"
ADOBE_ICC_PATH = ICC_DIR / "AdobeRGB1998.icc"
# sRGB profile is always available via Pillow (no file needed)
SRGB_PROFILE = ImageCms.createProfile("sRGB")
SRGB_ICC_BYTES = ImageCms.ImageCmsProfile(SRGB_PROFILE).tobytes()
def safe_open_profile(path: Path):
"""
Safely open ICC profile.
AdobeRGB1998 can legitimately be small (e.g., ~560 bytes), so do NOT size-gate.
"""
try:
if not path.exists():
print(f"⚠️ ICC missing: {path}")
return None
if not path.is_file():
print(f"⚠️ ICC is not a file: {path}")
return None
size = path.stat().st_size
print(f"ℹ️ ICC found: {path} ({size} bytes)")
prof = ImageCms.getOpenProfile(str(path))
return prof
except Exception as e:
print(f"⚠️ ICC open failed: {path} → {e}")
return None
ADOBE_PROFILE = safe_open_profile(ADOBE_ICC_PATH)
def convert_and_tag(img: Image.Image, target_color_space: str):
"""
Converts pixel values to the selected target profile AND returns ICC bytes
for embedding into the exported file.
Incoming images are treated as sRGB by default (practical for AI/web images).
"""
if img.mode != "RGB":
img = img.convert("RGB")
if target_color_space == "sRGB (Stock Standard)":
return img, SRGB_ICC_BYTES
if target_color_space == "Adobe RGB (1998)":
if ADOBE_PROFILE is None:
print("⚠️ AdobeRGB requested but profile not loaded; falling back to sRGB tagging.")
return img, SRGB_ICC_BYTES
try:
xform = ImageCms.buildTransformFromOpenProfiles(
SRGB_PROFILE,
ADOBE_PROFILE,
"RGB",
"RGB",
renderingIntent=0 # perceptual
)
out = ImageCms.applyTransform(img, xform)
return out, ADOBE_PROFILE.tobytes()
except Exception as e:
print(f"⚠️ AdobeRGB transform failed; falling back to sRGB. Error: {e}")
return img, SRGB_ICC_BYTES
return img, SRGB_ICC_BYTES
# =========================================================
# 1) THE PROCESSING ENGINE
# =========================================================
def process_image(image, output_format, color_space, fix_lighting, add_grain):
if image is None:
return None, None
# Convert PIL -> numpy
img_array = np.array(image)
# Stock safety: drop alpha if present
if img_array.ndim == 3 and img_array.shape[2] == 4:
img_array = cv2.cvtColor(img_array, cv2.COLOR_RGBA2RGB)
# RGB -> BGR for OpenCV
img_bgr = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
# A) LIGHTING FIX (CLAHE) - gentle
if fix_lighting:
lab = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(lab)
clahe = cv2.createCLAHE(clipLimit=1.5, tileGridSize=(8, 8))
l_fixed = clahe.apply(l)
lab_fixed = cv2.merge((l_fixed, a, b))
img_bgr = cv2.cvtColor(lab_fixed, cv2.COLOR_LAB2BGR)
# B) TEXTURE FIX (Monochromatic Film Grain)
if add_grain:
h, w = img_bgr.shape[:2]
# IMPORTANT: int16 prevents negative noise from wrapping under uint8
noise = np.random.normal(loc=0.0, scale=2.5, size=(h, w)).astype(np.int16)
img_i16 = img_bgr.astype(np.int16)
img_i16[:, :, 0] += noise
img_i16[:, :, 1] += noise
img_i16[:, :, 2] += noise
img_bgr = np.clip(img_i16, 0, 255).astype(np.uint8)
# Convert back to PIL RGB
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
final_pil = Image.fromarray(img_rgb).convert("RGB")
# C) COLOR SPACE: convert + embed ICC
final_pil, icc_bytes = convert_and_tag(final_pil, color_space)
# D) EXPORT: create a real file for download
suffix = ".jpg" if output_format == "JPEG" else ".png"
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
tmp_path = tmp.name
tmp.close()
if output_format == "JPEG":
final_pil.save(
tmp_path,
format="JPEG",
quality=100,
subsampling=0,
optimize=True,
icc_profile=icc_bytes,
)
else:
final_pil.save(
tmp_path,
format="PNG",
compress_level=6,
optimize=True,
icc_profile=icc_bytes,
)
return final_pil, tmp_path
# =========================================================
# 2) THE UI (Gradio)
# =========================================================
css = """
#run-btn {background-color: #ff7c00 !important; color: white !important;}
"""
with gr.Blocks(title="StockFix AI", css=css) as app:
gr.Markdown("## StockFix AI: De-Plasticizer")
# Friendly status line
if ADOBE_PROFILE is None:
gr.Markdown(
"⚠️ **Adobe RGB (1998) ICC not loaded.** "
"Upload `icc/AdobeRGB1998.icc` to enable true Adobe RGB exports."
)
else:
gr.Markdown("✅ Adobe RGB (1998) profile loaded.")
with gr.Row():
with gr.Column():
input_img = gr.Image(type="pil", label="Input AI Image")
with gr.Group():
gr.Markdown("### 1. Fixes")
chk_light = gr.Checkbox(label="Fix Flat Lighting (CLAHE)", value=True)
chk_grain = gr.Checkbox(label="Add Film Grain (Monochromatic)", value=True)
with gr.Group():
gr.Markdown("### 2. Color Profile")
radio_color = gr.Radio(
["sRGB (Stock Standard)", "Adobe RGB (1998)"],
label="Target Color Space",
value="sRGB (Stock Standard)",
)
with gr.Group():
gr.Markdown("### 3. Format")
radio_fmt = gr.Radio(["JPEG", "PNG"], label="Output Format", value="JPEG")
btn_run = gr.Button("Fix & Export", elem_id="run-btn")
with gr.Column():
output_img = gr.Image(label="Preview", type="pil")
output_file = gr.File(label="Download (PNG/JPEG)")
btn_run.click(
fn=process_image,
inputs=[input_img, radio_fmt, radio_color, chk_light, chk_grain],
outputs=[output_img, output_file],
)
if __name__ == "__main__":
app.launch() |