Shinhati2023 commited on
Commit
f49268a
·
verified ·
1 Parent(s): 9e1be05

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +75 -56
app.py CHANGED
@@ -5,103 +5,122 @@ from PIL import Image, ImageCms
5
  import io
6
 
7
  # --- 1. THE PROCESSING ENGINE ---
8
- def process_image(image, output_format, fix_lighting, add_grain):
9
- """
10
- Core function to de-AI the image and fix color space.
11
- """
12
  if image is None:
13
  return None
14
 
15
- # Convert PIL Image to OpenCV format (numpy array)
16
- # PIL uses RGB, OpenCV uses BGR
17
  img_array = np.array(image)
 
 
 
 
18
  img_bgr = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
19
 
20
- # A. LIGHTING FIX (CLAHE)
21
- # This fixes the "flat" look by maximizing local contrast
22
  if fix_lighting:
23
- # Convert to LAB to operate only on Lightness (L)
24
  lab = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2LAB)
25
  l, a, b = cv2.split(lab)
26
-
27
- # Apply CLAHE (Contrast Limited Adaptive Histogram Equalization)
28
- # clipLimit=2.0 is a safe "stock" value. 3.0+ gets too dramatic.
29
- clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
30
  l_fixed = clahe.apply(l)
31
-
32
- # Merge back
33
  lab_fixed = cv2.merge((l_fixed, a, b))
34
  img_bgr = cv2.cvtColor(lab_fixed, cv2.COLOR_LAB2BGR)
35
 
36
- # B. TEXTURE FIX (Film Grain)
37
- # Adds organic noise to prevent "plastic" rejection
38
  if add_grain:
39
  h, w, c = img_bgr.shape
40
- # Generate gaussian noise (std-dev of 3 is subtle but effective)
41
- noise = np.random.normal(0, 3, (h, w, c)).astype(np.uint8)
42
- img_bgr = cv2.add(img_bgr, noise)
 
 
 
 
 
43
 
44
- # C. COLOR SPACE FIX (Force sRGB)
45
- # Convert back to PIL RGB for final color management
46
  img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
47
  final_pil = Image.fromarray(img_rgb)
48
-
49
- # Create the standard sRGB profile
 
50
  srgb_profile = ImageCms.createProfile("sRGB")
51
 
52
- # D. EXPORT FORMATTING
53
- # We save to a bytes buffer to return to Gradio
54
- output_buffer = io.BytesIO()
 
 
55
 
56
- save_kwargs = {
57
- "icc_profile": ImageCms.ImageCmsProfile(srgb_profile).tobytes()
58
- }
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  if output_format == "JPEG":
61
- # Stock sites prefer JPEGs saved at Quality 95-100, not maximum subsampling
62
- final_pil.save(output_buffer, format="JPEG", quality=95, subsampling=0, **save_kwargs)
63
- output_filename = "colorfix_stock_ready.jpg"
64
  else:
65
- # PNG is lossless
66
  final_pil.save(output_buffer, format="PNG", **save_kwargs)
67
- output_filename = "colorfix_stock_ready.png"
68
 
69
- # Reset buffer position
70
  output_buffer.seek(0)
71
-
72
- # Return path for Gradio to display/download
73
- # Note: Gradio handles the file wrapping, we just return the PIL image or path.
74
- # For a robust download, we return the tuple (filepath, filepath) logic usually,
75
- # but Gradio 4.x simplifies this. We will return the PIL image,
76
- # and Gradio's "Image" component handles the download if interactive=False.
77
-
78
- return final_pil
79
 
80
  # --- 2. THE UI (Gradio) ---
81
- with gr.Blocks(title="Stock Photo Fixer AI") as app:
82
- gr.Markdown("## 📸 AI ColorFix & Stock Compliance Engine")
83
- gr.Markdown("Fix flat AI lighting, add organic grain, and force sRGB compliance for Adobe Stock/Shutterstock.")
84
 
 
 
 
85
  with gr.Row():
86
  with gr.Column():
87
- input_img = gr.Image(type="pil", label="Drop AI Image Here")
88
 
89
  with gr.Group():
90
- gr.Markdown("### Settings")
91
- fmt_choice = gr.Radio(["JPEG", "PNG"], label="Output Format", value="JPEG")
92
  chk_light = gr.Checkbox(label="Fix Flat Lighting (CLAHE)", value=True)
93
- chk_grain = gr.Checkbox(label="Add Organic Grain (Anti-Banding)", value=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
- btn_run = gr.Button("Fix Image", variant="primary")
96
 
97
  with gr.Column():
98
- # The output is interactive=False so it shows a download button automatically
99
- output_img = gr.Image(label="Stock Ready Result", type="pil", format="jpeg")
100
 
101
- # Wire the button
102
  btn_run.click(
103
  fn=process_image,
104
- inputs=[input_img, fmt_choice, chk_light, chk_grain],
105
  outputs=output_img
106
  )
107
 
 
5
  import io
6
 
7
  # --- 1. THE PROCESSING ENGINE ---
8
+ def process_image(image, output_format, color_space, fix_lighting, add_grain):
 
 
 
9
  if image is None:
10
  return None
11
 
12
+ # Convert to OpenCV format (BGR)
 
13
  img_array = np.array(image)
14
+ # Check if image is RGB or RGBA (drop alpha if present for stock safety)
15
+ if img_array.shape[2] == 4:
16
+ img_array = cv2.cvtColor(img_array, cv2.COLOR_RGBA2RGB)
17
+
18
  img_bgr = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
19
 
20
+ # A. LIGHTING FIX (CLAHE) - Now gentler
 
21
  if fix_lighting:
 
22
  lab = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2LAB)
23
  l, a, b = cv2.split(lab)
24
+ # Reduced clipLimit from 2.0 to 1.5 for a more natural look
25
+ clahe = cv2.createCLAHE(clipLimit=1.5, tileGridSize=(8, 8))
 
 
26
  l_fixed = clahe.apply(l)
 
 
27
  lab_fixed = cv2.merge((l_fixed, a, b))
28
  img_bgr = cv2.cvtColor(lab_fixed, cv2.COLOR_LAB2BGR)
29
 
30
+ # B. TEXTURE FIX (Film Grain) - NOW MONOCHROMATIC
 
31
  if add_grain:
32
  h, w, c = img_bgr.shape
33
+ # Generate ONE channel of noise (grayscale)
34
+ # Reduced sigma to 2.5 for subtle texture
35
+ noise_gray = np.random.normal(0, 2.5, (h, w)).astype(np.uint8)
36
+ # Stack it to match image channels (so noise is identical on R, G, and B)
37
+ noise_rgb = np.dstack([noise_gray, noise_gray, noise_gray])
38
+
39
+ # Add noise (using cv2.add ensures we don't rollover 255)
40
+ img_bgr = cv2.add(img_bgr, noise_rgb)
41
 
42
+ # C. COLOR SPACE MANAGEMENT
 
43
  img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
44
  final_pil = Image.fromarray(img_rgb)
45
+
46
+ # Define Profiles
47
+ # sRGB is built-in to Pillow
48
  srgb_profile = ImageCms.createProfile("sRGB")
49
 
50
+ # Adobe RGB is tricky without an external .icc file.
51
+ # If the user selects Adobe RGB, we will attempt to tag it,
52
+ # but for STOCK SAFETY, we default to sRGB logic if file is missing.
53
+ # Ideally, you would load an actual "AdobeRGB1998.icc" file here.
54
+ # For now, we will handle the conversion logic safely.
55
 
56
+ output_buffer = io.BytesIO()
57
+ save_kwargs = {}
 
58
 
59
+ if color_space == "sRGB (Stock Standard)":
60
+ # Force conversion to sRGB
61
+ if final_pil.mode != 'RGB':
62
+ final_pil = final_pil.convert('RGB')
63
+ save_kwargs["icc_profile"] = ImageCms.ImageCmsProfile(srgb_profile).tobytes()
64
+
65
+ elif color_space == "Adobe RGB (1998)":
66
+ # NOTE: To do this perfectly, you need the .icc file.
67
+ # Since we are in a script, we will tag it as sRGB but try to preserve
68
+ # wider data if possible, or you can point to a file on your disk.
69
+ # For this snippet, we will keep the pixels raw but save with sRGB tag
70
+ # to prevent "Untagged" rejection, OR if you have the file, uncomment below:
71
+ # adobe_profile = ImageCms.getOpenProfile("path/to/AdobeRGB1998.icc")
72
+ # save_kwargs["icc_profile"] = adobe_profile.tobytes()
73
+
74
+ # Fallback for code-only: Save with sRGB tag but warn user
75
+ save_kwargs["icc_profile"] = ImageCms.ImageCmsProfile(srgb_profile).tobytes()
76
+
77
+ # D. EXPORT
78
  if output_format == "JPEG":
79
+ final_pil.save(output_buffer, format="JPEG", quality=100, subsampling=0, **save_kwargs)
 
 
80
  else:
 
81
  final_pil.save(output_buffer, format="PNG", **save_kwargs)
 
82
 
 
83
  output_buffer.seek(0)
84
+ return Image.open(output_buffer)
 
 
 
 
 
 
 
85
 
86
  # --- 2. THE UI (Gradio) ---
87
+ css = """
88
+ #run-btn {background-color: #ff7c00 !important; color: white !important;}
89
+ """
90
 
91
+ with gr.Blocks(title="StockFix AI", css=css) as app:
92
+ gr.Markdown("## 📸 StockFix AI: De-Plasticizer")
93
+
94
  with gr.Row():
95
  with gr.Column():
96
+ input_img = gr.Image(type="pil", label="Input AI Image")
97
 
98
  with gr.Group():
99
+ gr.Markdown("### 1. Fixes")
 
100
  chk_light = gr.Checkbox(label="Fix Flat Lighting (CLAHE)", value=True)
101
+ chk_grain = gr.Checkbox(label="Add Film Grain (Monochromatic)", value=True)
102
+
103
+ with gr.Group():
104
+ gr.Markdown("### 2. Color Profile")
105
+ # HERE IS YOUR NEW BUTTON SET
106
+ radio_color = gr.Radio(
107
+ ["sRGB (Stock Standard)", "Adobe RGB (1998)"],
108
+ label="Target Color Space",
109
+ value="sRGB (Stock Standard)"
110
+ )
111
+
112
+ with gr.Group():
113
+ gr.Markdown("### 3. Format")
114
+ radio_fmt = gr.Radio(["JPEG", "PNG"], label="Output Format", value="JPEG")
115
 
116
+ btn_run = gr.Button("Fix & Export", elem_id="run-btn")
117
 
118
  with gr.Column():
119
+ output_img = gr.Image(label="Stock Ready Result", type="pil")
 
120
 
 
121
  btn_run.click(
122
  fn=process_image,
123
+ inputs=[input_img, radio_fmt, radio_color, chk_light, chk_grain],
124
  outputs=output_img
125
  )
126