Shinhati2023 commited on
Commit
7f7be37
·
verified ·
1 Parent(s): 4f32d07

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -28
app.py CHANGED
@@ -1,29 +1,75 @@
 
 
 
1
  import gradio as gr
2
  import cv2
3
  import numpy as np
4
  from PIL import Image, ImageCms
5
- import tempfile
6
- import os
7
-
8
- # --- ICC PROFILE (cached once) ---
9
- _SRGB_ICC = ImageCms.ImageCmsProfile(ImageCms.createProfile("sRGB")).tobytes()
10
 
11
- # --- 1. THE PROCESSING ENGINE ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  def process_image(image, output_format, color_space, fix_lighting, add_grain):
13
  if image is None:
14
  return None, None
15
 
16
- # Convert PIL -> numpy (RGB)
17
  img_array = np.array(image)
18
 
19
- # Drop alpha if present (stock-safe)
20
  if img_array.ndim == 3 and img_array.shape[2] == 4:
21
  img_array = cv2.cvtColor(img_array, cv2.COLOR_RGBA2RGB)
22
 
23
- # Convert RGB -> BGR for OpenCV ops
24
  img_bgr = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
25
 
26
- # A) LIGHTING FIX (gentle CLAHE)
27
  if fix_lighting:
28
  lab = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2LAB)
29
  l, a, b = cv2.split(lab)
@@ -32,11 +78,12 @@ def process_image(image, output_format, color_space, fix_lighting, add_grain):
32
  lab_fixed = cv2.merge((l_fixed, a, b))
33
  img_bgr = cv2.cvtColor(lab_fixed, cv2.COLOR_LAB2BGR)
34
 
35
- # B) TEXTURE FIX (Monochromatic Film Grain) fixed + faster
36
  if add_grain:
37
  h, w = img_bgr.shape[:2]
38
 
39
- # Use int16 so negative noise doesn't wrap (uint8 would wrap!)
 
40
  noise = np.random.normal(loc=0.0, scale=2.5, size=(h, w)).astype(np.int16)
41
 
42
  img_i16 = img_bgr.astype(np.int16)
@@ -46,38 +93,48 @@ def process_image(image, output_format, color_space, fix_lighting, add_grain):
46
 
47
  img_bgr = np.clip(img_i16, 0, 255).astype(np.uint8)
48
 
49
- # C) Convert back to PIL (RGB)
50
  img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
51
  final_pil = Image.fromarray(img_rgb).convert("RGB")
52
 
53
- # D) Color profile tagging (keep it stock-safe)
54
- save_kwargs = {}
55
- if color_space == "sRGB (Stock Standard)":
56
- save_kwargs["icc_profile"] = _SRGB_ICC
57
- elif color_space == "Adobe RGB (1998)":
58
- # Without an actual AdobeRGB1998.icc file, safest fallback is still sRGB tagging.
59
- # (Prevents "untagged" rejections.)
60
- save_kwargs["icc_profile"] = _SRGB_ICC
61
 
62
- # E) Write a real downloadable file (this is what prevents “WebP only)
63
  suffix = ".jpg" if output_format == "JPEG" else ".png"
64
  tmp = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
65
  tmp_path = tmp.name
66
  tmp.close()
67
 
68
  if output_format == "JPEG":
69
- final_pil.save(tmp_path, format="JPEG", quality=100, subsampling=0, optimize=True, **save_kwargs)
 
 
 
 
 
 
 
 
70
  else:
71
- # compress_level 6 is a good speed/size balance
72
- final_pil.save(tmp_path, format="PNG", compress_level=6, optimize=True, **save_kwargs)
 
 
 
 
 
 
73
 
74
  # Return:
75
- # - Preview image (Gradio may serve this as WebP in the UI; thats fine)
76
- # - File path for true PNG/JPEG download
77
  return final_pil, tmp_path
78
 
79
 
80
- # --- 2. THE UI (Gradio) ---
 
 
81
  css = """
82
  #run-btn {background-color: #ff7c00 !important; color: white !important;}
83
  """
@@ -85,6 +142,12 @@ css = """
85
  with gr.Blocks(title="StockFix AI", css=css) as app:
86
  gr.Markdown("## StockFix AI: De-Plasticizer")
87
 
 
 
 
 
 
 
88
  with gr.Row():
89
  with gr.Column():
90
  input_img = gr.Image(type="pil", label="Input AI Image")
@@ -110,6 +173,7 @@ with gr.Blocks(title="StockFix AI", css=css) as app:
110
 
111
  with gr.Column():
112
  output_img = gr.Image(label="Preview", type="pil")
 
113
  output_file = gr.File(label="Download (PNG/JPEG)")
114
 
115
  btn_run.click(
 
1
+ import os
2
+ import tempfile
3
+
4
  import gradio as gr
5
  import cv2
6
  import numpy as np
7
  from PIL import Image, ImageCms
 
 
 
 
 
8
 
9
+ # =========================================================
10
+ # NEW: Color profile assets + caching
11
+ # =========================================================
12
+ # sRGB profile is always available via Pillow
13
+ SRGB_PROFILE = ImageCms.createProfile("sRGB")
14
+ SRGB_ICC_BYTES = ImageCms.ImageCmsProfile(SRGB_PROFILE).tobytes()
15
+
16
+ # NEW FILE YOU WILL CREATE IN YOUR SPACE:
17
+ # profiles/AdobeRGB1998.icc
18
+ ADOBE_ICC_PATH = os.path.join("profiles", "AdobeRGB1998.icc")
19
+ ADOBE_PROFILE = ImageCms.getOpenProfile(ADOBE_ICC_PATH) if os.path.exists(ADOBE_ICC_PATH) else None
20
+
21
+
22
+ def convert_and_tag(img: Image.Image, target_color_space: str):
23
+ """
24
+ NEW: Converts pixel values to the selected target profile AND returns the correct ICC bytes.
25
+ This is what makes sRGB vs AdobeRGB(1998) "real" for stock delivery.
26
+ """
27
+ if img.mode != "RGB":
28
+ img = img.convert("RGB")
29
+
30
+ # For most AI/web images, treating the working space as sRGB is the practical default.
31
+ # (If you want full source-ICC detection later, we can add it.)
32
+ if target_color_space == "sRGB (Stock Standard)":
33
+ return img, SRGB_ICC_BYTES
34
+
35
+ if target_color_space == "Adobe RGB (1998)":
36
+ # If the profile file isn't present, we safely fall back to sRGB to avoid untagged/mismatched exports.
37
+ if ADOBE_PROFILE is None:
38
+ return img, SRGB_ICC_BYTES
39
+
40
+ # Convert from sRGB -> AdobeRGB1998
41
+ xform = ImageCms.buildTransformFromOpenProfiles(
42
+ SRGB_PROFILE,
43
+ ADOBE_PROFILE,
44
+ "RGB",
45
+ "RGB",
46
+ renderingIntent=0 # perceptual
47
+ )
48
+ out = ImageCms.applyTransform(img, xform)
49
+ return out, ADOBE_PROFILE.tobytes()
50
+
51
+ # Fallback safety
52
+ return img, SRGB_ICC_BYTES
53
+
54
+
55
+ # =========================================================
56
+ # 1) THE PROCESSING ENGINE
57
+ # =========================================================
58
  def process_image(image, output_format, color_space, fix_lighting, add_grain):
59
  if image is None:
60
  return None, None
61
 
62
+ # Convert PIL -> numpy
63
  img_array = np.array(image)
64
 
65
+ # Stock safety: drop alpha if present
66
  if img_array.ndim == 3 and img_array.shape[2] == 4:
67
  img_array = cv2.cvtColor(img_array, cv2.COLOR_RGBA2RGB)
68
 
69
+ # RGB -> BGR for OpenCV
70
  img_bgr = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
71
 
72
+ # A) LIGHTING FIX (CLAHE) - gentle
73
  if fix_lighting:
74
  lab = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2LAB)
75
  l, a, b = cv2.split(lab)
 
78
  lab_fixed = cv2.merge((l_fixed, a, b))
79
  img_bgr = cv2.cvtColor(lab_fixed, cv2.COLOR_LAB2BGR)
80
 
81
+ # B) TEXTURE FIX (Monochromatic Film Grain) - FIXED + better
82
  if add_grain:
83
  h, w = img_bgr.shape[:2]
84
 
85
+ # IMPORTANT FIX:
86
+ # Use int16 noise so negative noise doesn't wrap like uint8.
87
  noise = np.random.normal(loc=0.0, scale=2.5, size=(h, w)).astype(np.int16)
88
 
89
  img_i16 = img_bgr.astype(np.int16)
 
93
 
94
  img_bgr = np.clip(img_i16, 0, 255).astype(np.uint8)
95
 
96
+ # Convert back to PIL RGB
97
  img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
98
  final_pil = Image.fromarray(img_rgb).convert("RGB")
99
 
100
+ # C) COLOR SPACE: REAL convert + embed ICC
101
+ final_pil, icc_bytes = convert_and_tag(final_pil, color_space)
 
 
 
 
 
 
102
 
103
+ # D) EXPORT: create a real file for download (prevents "webp-only" confusion)
104
  suffix = ".jpg" if output_format == "JPEG" else ".png"
105
  tmp = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
106
  tmp_path = tmp.name
107
  tmp.close()
108
 
109
  if output_format == "JPEG":
110
+ # Stock-friendly: no chroma subsampling for max quality
111
+ final_pil.save(
112
+ tmp_path,
113
+ format="JPEG",
114
+ quality=100,
115
+ subsampling=0,
116
+ optimize=True,
117
+ icc_profile=icc_bytes,
118
+ )
119
  else:
120
+ # Balanced speed/size
121
+ final_pil.save(
122
+ tmp_path,
123
+ format="PNG",
124
+ compress_level=6,
125
+ optimize=True,
126
+ icc_profile=icc_bytes,
127
+ )
128
 
129
  # Return:
130
+ # - Preview image (Gradio may serve preview as WebP; that's just UI)
131
+ # - Download file (REAL PNG/JPEG)
132
  return final_pil, tmp_path
133
 
134
 
135
+ # =========================================================
136
+ # 2) THE UI (Gradio)
137
+ # =========================================================
138
  css = """
139
  #run-btn {background-color: #ff7c00 !important; color: white !important;}
140
  """
 
142
  with gr.Blocks(title="StockFix AI", css=css) as app:
143
  gr.Markdown("## StockFix AI: De-Plasticizer")
144
 
145
+ # NEW: We show a small note if Adobe ICC is missing
146
+ if ADOBE_PROFILE is None:
147
+ gr.Markdown(
148
+ "⚠️ **Adobe RGB (1998) ICC not found.** Add `profiles/AdobeRGB1998.icc` to enable true Adobe RGB exports."
149
+ )
150
+
151
  with gr.Row():
152
  with gr.Column():
153
  input_img = gr.Image(type="pil", label="Input AI Image")
 
173
 
174
  with gr.Column():
175
  output_img = gr.Image(label="Preview", type="pil")
176
+ # NEW: This creates a real downloadable file (PNG/JPEG)
177
  output_file = gr.File(label="Download (PNG/JPEG)")
178
 
179
  btn_run.click(