返回 ppt-master
gemini_watermark_remover.py
根目录 / skills / ppt-master / scripts / gemini_watermark_remover.py
1 #!/usr/bin/env python3
2 """
3 PPT Master - Gemini Watermark Remover
4
5 Removes the watermark logo from the bottom-right corner of Gemini-generated images.
6 Uses a reverse blending algorithm to restore original pixels.
7
8 Usage:
9 python3 scripts/gemini_watermark_remover.py <image_path>
10 python3 scripts/gemini_watermark_remover.py <image_path> -o output_path.png
11
12 Examples:
13 python3 scripts/gemini_watermark_remover.py projects/demo/images/bg_01.png
14 python3 scripts/gemini_watermark_remover.py image.jpg -o image_clean.jpg
15
16 Dependencies:
17 pip install Pillow numpy
18
19 Notes:
20 - Supports PNG, JPG, JPEG formats
21 - Automatically detects watermark size (48px or 96px)
22 - Output file defaults to adding an _unwatermarked suffix
23 """
24
25 import sys
26 import argparse
27 from pathlib import Path
28
29 # Import modules from the same directory
30 sys.path.insert(0, str(Path(__file__).parent))
31
32 from console_encoding import configure_utf8_stdio
33
34 configure_utf8_stdio()
35
36 import numpy as np
37 from PIL import Image
38
39 # Algorithm parameters
40 ALPHA_THRESHOLD = 0.002 # Alpha threshold; values below this are not processed
41 MAX_ALPHA = 0.99 # Maximum alpha value to prevent division by zero
42 LOGO_VALUE = 255 # Logo pixel value (white)
43 LARGE_IMAGE_THRESHOLD = 1024
44 LARGE_LOGO_SIZE = 96
45 SMALL_LOGO_SIZE = 48
46 LARGE_MARGIN = 64
47 SMALL_MARGIN = 32
48
49 # Watermark background image paths
50 SCRIPT_DIR = Path(__file__).parent
51 BG_48_PATH = SCRIPT_DIR / "assets" / "bg_48.png"
52 BG_96_PATH = SCRIPT_DIR / "assets" / "bg_96.png"
53
54
55 def detect_watermark_config(width: int, height: int) -> dict[str, int]:
56 """
57 Detect watermark configuration based on image dimensions
58
59 Args:
60 width: Image width
61 height: Image height
62
63 Returns:
64 Configuration dict containing logo_size, margin_right, margin_bottom
65 """
66 if width > LARGE_IMAGE_THRESHOLD and height > LARGE_IMAGE_THRESHOLD:
67 return {
68 "logo_size": LARGE_LOGO_SIZE,
69 "margin_right": LARGE_MARGIN,
70 "margin_bottom": LARGE_MARGIN,
71 }
72 return {
73 "logo_size": SMALL_LOGO_SIZE,
74 "margin_right": SMALL_MARGIN,
75 "margin_bottom": SMALL_MARGIN,
76 }
77
78
79 def calculate_watermark_position(width: int, height: int, config: dict[str, int]) -> dict[str, int]:
80 """
81 Calculate watermark position
82
83 Args:
84 width: Image width
85 height: Image height
86 config: Watermark configuration
87
88 Returns:
89 Position dict containing x, y, width, height
90 """
91 logo_size = config["logo_size"]
92 return {
93 "x": width - config["margin_right"] - logo_size,
94 "y": height - config["margin_bottom"] - logo_size,
95 "width": logo_size,
96 "height": logo_size,
97 }
98
99
100 def calculate_alpha_map(bg_image: Image.Image) -> np.ndarray:
101 """
102 Calculate the alpha channel map from the watermark background image
103
104 Args:
105 bg_image: Watermark background PNG image
106
107 Returns:
108 Alpha map array (range 0-1)
109 """
110 bg_array = np.array(bg_image.convert("RGB"), dtype=np.float32)
111 max_channel = np.max(bg_array, axis=2)
112 return max_channel / 255.0
113
114
115 def remove_watermark(image: Image.Image, alpha_map: np.ndarray, position: dict) -> Image.Image:
116 """
117 Remove watermark using a reverse blending algorithm
118
119 Args:
120 image: Original image
121 alpha_map: Alpha map array
122 position: Watermark position
123
124 Returns:
125 Image with watermark removed
126 """
127 img_array = np.array(image.convert("RGBA"), dtype=np.float32)
128 x, y, w, h = position["x"], position["y"], position["width"], position["height"]
129
130 for row in range(h):
131 for col in range(w):
132 alpha = alpha_map[row, col]
133 if alpha < ALPHA_THRESHOLD:
134 continue
135 alpha = min(alpha, MAX_ALPHA)
136 one_minus_alpha = 1.0 - alpha
137
138 img_y, img_x = y + row, x + col
139 for c in range(3):
140 watermarked = img_array[img_y, img_x, c]
141 original = (watermarked - alpha * LOGO_VALUE) / one_minus_alpha
142 img_array[img_y, img_x, c] = np.clip(original, 0, 255)
143
144 return Image.fromarray(img_array.astype(np.uint8))
145
146
147 def process_image(input_path: Path, output_path: Path | None = None, verbose: bool = True) -> Path:
148 """
149 Process a single image to remove its watermark
150
151 Args:
152 input_path: Input image path
153 output_path: Output image path (optional)
154 verbose: Whether to output detailed information
155
156 Returns:
157 Output file path
158 """
159 image = Image.open(input_path)
160 width, height = image.size
161
162 config = detect_watermark_config(width, height)
163 position = calculate_watermark_position(width, height, config)
164
165 if verbose:
166 print(f" Image size: {width} x {height}")
167 print(f" Watermark size: {config['logo_size']} x {config['logo_size']}")
168 print(f" Watermark position: ({position['x']}, {position['y']})")
169
170 bg_path = BG_96_PATH if config["logo_size"] == 96 else BG_48_PATH
171
172 if not bg_path.exists():
173 print(f"Error: Watermark background image not found: {bg_path}")
174 sys.exit(1)
175
176 bg_image = Image.open(bg_path)
177 alpha_map = calculate_alpha_map(bg_image)
178
179 result = remove_watermark(image, alpha_map, position)
180
181 if output_path is None:
182 stem = input_path.stem
183 suffix = input_path.suffix or ".png"
184 output_path = input_path.parent / f"{stem}_unwatermarked{suffix}"
185
186 if output_path.suffix.lower() in (".jpg", ".jpeg"):
187 result = result.convert("RGB")
188
189 result.save(output_path)
190 return output_path
191
192
193 def main() -> None:
194 """Run the CLI entry point."""
195 parser = argparse.ArgumentParser(
196 description='PPT Master - Gemini Watermark Remover',
197 formatter_class=argparse.RawDescriptionHelpFormatter,
198 epilog='''
199 Examples:
200 %(prog)s projects/demo/images/bg_01.png
201 %(prog)s image.jpg -o image_clean.jpg
202
203 Notes:
204 - Automatically detects watermark size (96px for large images, 48px for small)
205 - Supports PNG, JPG, JPEG formats
206 - Output file defaults to adding an _unwatermarked suffix
207 '''
208 )
209
210 parser.add_argument('input', type=Path, help='Input image path')
211 parser.add_argument('-o', '--output', type=Path, default=None, help='Output image path')
212 parser.add_argument('-q', '--quiet', action='store_true', help='Quiet mode')
213
214 args = parser.parse_args()
215
216 if not args.input.exists():
217 print(f"Error: File not found: {args.input}")
218 sys.exit(1)
219
220 verbose = not args.quiet
221 if verbose:
222 print("PPT Master - Gemini Watermark Remover")
223 print("=" * 40)
224 print(f" Input file: {args.input}")
225
226 output = process_image(args.input, args.output, verbose=verbose)
227
228 if verbose:
229 print()
230 print(f"[Done] Saved to: {output}")
231
232
233 if __name__ == "__main__":
234 main()
235
235 lines PYTHON