commit a87d111e1b407ede06440567681c8b33255a63e8 Author: Jad Date: Fri Dec 19 17:10:36 2025 +0800 init commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6a78e83 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Jad + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6edc369 --- /dev/null +++ b/README.md @@ -0,0 +1,179 @@ +[中文文档](README_zh.md) + +# Gemini Lossless Watermark Remover - [banana.ovo.re](https://banana.ovo.re) + +A high-performance, 100% client-side tool for removing Gemini AI watermarks. Built with pure JavaScript, it leverages a mathematically precise **Reverse Alpha Blending** algorithm rather than unpredictable AI inpainting. + +

+ +

+ +## Features + +- ✅ **100% Client-side** - No backend, no server-side processing. Your data stays in your browser. +- ✅ **Privacy-First** - Images are never uploaded to any server. Period. +- ✅ **Mathematical Precision** - Based on the Reverse Alpha Blending formula, not "hallucinating" AI models. +- ✅ **Auto-Detection** - Intelligent recognition of 48×48 or 96×96 watermark variants. +- ✅ **User Friendly** - Simple drag-and-drop interface with instant processing. +- ✅ **Cross-Platform** - Runs smoothly on all modern web browsers. + +## Examples + +
+Click to Expand/Collapse Examples +  + +| Original Image | Watermark Removed | +| :---: | :----: | +| | | +| | | +| | | +| | | +| | | + +
+ +## ⚠️ Disclaimer + +> **USE AT YOUR OWN RISK** +> +> This tool modifies image files. While it is designed to work reliably, unexpected results may occur due to: +> - Variations in Gemini's watermark implementation +> - Corrupted or unusual image formats +> - Edge cases not covered by testing +> +> The author assumes no responsibility for any data loss, image corruption, or unintended modifications. By using this tool, you acknowledge that you understand these risks. + +## Usage + +1. Open `index.html` in your browser (or visit the hosted link). +2. Drag and drop or click to select your Gemini-generated image. +3. The engine will automatically process and remove the watermark. +4. Download the cleaned image. + +## How it Works + +### The Gemini Watermarking Process + +Gemini applies watermarks using standard alpha compositing: + +$$watermarked = \alpha \cdot logo + (1 - \alpha) \cdot original$$ + +Where: +- `watermarked`: The pixel value with the watermark. +- `α`: The Alpha channel value (0.0 - 1.0). +- `logo`: The watermark logo color value (White = 255). +- `original`: The raw, original pixel value we want to recover. + +### The Reverse Solution + +To remove the watermark, we solve for `original`: + +$$original = \frac{watermarked - \alpha \cdot logo}{1 - \alpha}$$ + +By capturing the watermark on a known solid background, we reconstruct the exact Alpha map and apply the inverse formula to restore the original pixels with zero loss. + +## Detection Rules + +| Image Dimension Condition | Watermark Size | Right Margin | Bottom Margin | +| :--- | :--- | :--- | :--- | +| Width > 1024 **AND** Height > 1024 | 96×96 | 64px | 64px | +| Otherwise | 48×48 | 32px | 32px | + +## Project Structure + +```text +gemini-watermark-web/ +├── index.html # Main entry point +├── css/ +│ └── style.css # UI Styling +├── js/ +│ ├── core/ +│ │ ├── alphaMap.js # Alpha map calculation logic +│ │ ├── blendModes.js # Implementation of Reverse Alpha Blending +│ │ └── watermarkEngine.js # Main engine coordinator +│ ├── assets/ +│ │ ├── bg-capture-48.png # Pre-captured 48×48 watermark map +│ │ └── bg-capture-96.png # Pre-captured 96×96 watermark map +│ └── app.js # UI Interaction & Event handling +└── README.md +``` + +## Core Modules + +### alphaMap.js + +Calculates the Alpha channel by comparing captured watermark assets: + +```javascript +export function calculateAlphaMap(bgCaptureImageData) { + // Extract max RGB channel and normalize to [0, 1] + const alphaMap = new Float32Array(width * height); + for (let i = 0; i < alphaMap.length; i++) { + const maxChannel = Math.max(r, g, b); + alphaMap[i] = maxChannel / 255.0; + } + return alphaMap; +} +``` + +### blendModes.js + +The mathematical core of the tool: + +```javascript +export function removeWatermark(imageData, alphaMap, position) { + // Formula: original = (watermarked - α × 255) / (1 - α) + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const alpha = Math.min(alphaMap[idx], MAX_ALPHA); + const original = (watermarked - alpha * 255) / (1.0 - alpha); + imageData.data[idx] = Math.max(0, Math.min(255, original)); + } + } +} +``` + +## Browser Compatibility + +- ✅ Chrome 90+ +- ✅ Firefox 88+ +- ✅ Safari 14+ +- ✅ Edge 90+ + +Required APIs: +- ES6 Modules +- Canvas API +- Async/Await +- TypedArray (Float32Array, Uint8ClampedArray) + +--- + +## Limitations + +- Only removes **Gemini visible watermarks** (the semi-transparent logo in bottom-right) +- Does not remove invisible/steganographic watermarks. [(Learn more about SynthID)](https://support.google.com/gemini/answer/16722517) +- Designed for Gemini's current watermark pattern (as of 2025) + +## Legal Disclaimer + +This tool is provided for **personal and educational use only**. + +The removal of watermarks may have legal implications depending on your jurisdiction and the intended use of the images. Users are solely responsible for ensuring their use of this tool complies with applicable laws, terms of service, and intellectual property rights. + +The author does not condone or encourage the misuse of this tool for copyright infringement, misrepresentation, or any other unlawful purposes. + +**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM THE USE OF THIS SOFTWARE.** + +## License + +[MIT License](./LICENSE) + +## Related Links + +- [Gemini Watermark Tool](https://github.com/allenk/GeminiWatermarkTool) +- [Removing Gemini AI Watermarks: A Deep Dive into Reverse Alpha Blending](https://allenkuo.medium.com/removing-gemini-ai-watermarks-a-deep-dive-into-reverse-alpha-blending-bbbd83af2a3f) + +## Credits + +This project is a JavaScript port of the [Gemini Watermark Tool](https://github.com/allenk/GeminiWatermarkTool) C++ implementation. \ No newline at end of file diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 0000000..b967198 --- /dev/null +++ b/README_zh.md @@ -0,0 +1,200 @@ +[English](README.md) + +# Gemini 无损去水印工具 - [banana.ovo.re](https://banana.ovo.re) + +基于 Javascript 的纯浏览器端 Gemini AI 图像无损去水印工具,使用数学精确的反向 Alpha 混合算法 + +

+ +

+ +## 特性 + +- ✅ **纯浏览器端处理** - 无需后端服务器,所有处理在本地完成 +- ✅ **隐私保护** - 图片不会上传到任何服务器 +- ✅ **数学精确** - 基于反向 Alpha 混合算法,非 AI 模型 +- ✅ **自动检测** - 自动识别 48×48 或 96×96 水印尺寸 +- ✅ **易于使用** - 拖拽选择图片,一键处理 +- ✅ **跨平台** - 支持所有现代浏览器 + +## 效果示例 + +
+点击查看/收起示例 +  + +| 原图 | 去水印后 | +| :---: | :----: | +| | | +| | | +| | | +| | | +| | | + +
+ +## ⚠️ 使用需注意 + +> **使用此工具产生的风险由用户自行承担** +> +> 本工具涉及对图像数据的修改。尽管在设计上力求处理结果的可靠性,但由于以下因素,仍可能产生非预期的处理结果: +> - Gemini 水印实现方式的更新或变动 +> - 图像文件损坏或使用了非标准格式 +> - 测试案例未能覆盖的边界情况 +> +> 作者对任何形式的数据丢失、图像损坏或非预期的修改结果不承担法律责任。使用本工具即代表您已了解并接受上述风险。 + +## 使用方法 + +1. 在浏览器中打开 `index.html` +2. 拖拽或点击选择带水印的 Gemini 图片 +3. 图片会自动开始处理,移除水印 +4. 下载处理后的图片 + +## 算法原理 + +### Gemini 添加水印的方式 + +Gemini 通过以下方式添加水印: + +$$watermarked = \alpha \cdot logo + (1 - \alpha) \cdot original$$ + +其中: +- `watermarked`: 带水印的像素值 +- `α`: Alpha 通道值 (0.0-1.0) +- `logo`: 水印 logo 的颜色值(白色 = 255) +- `original`: 原始像素值 + +### 反向求解移除水印 + +为了去除水印,可以反向求解如下: + +$$original = \frac{watermarked - \alpha \cdot logo}{1 - \alpha}$$ + +通过在纯色背景上捕获水印,我们可以重建 Alpha 通道,然后应用反向公式恢复原始图像 + +## 水印检测规则 + +| 图像尺寸条件 | 水印尺寸 | 右边距 | 下边距 | +|------------|---------|--------|--------| +| 宽 > 1024 **且** 高 > 1024 | 96×96 | 64px | 64px | +| 其他情况 | 48×48 | 32px | 32px | + +## 项目结构 + +``` +gemini-watermark-web/ +├── index.html # 主页面 +├── css/ +│ └── style.css # 样式文件 +├── js/ +│ ├── core/ +│ │ ├── alphaMap.js # Alpha map 计算 +│ │ ├── blendModes.js # 反向 alpha 混合算法 +│ │ └── watermarkEngine.js # 主引擎 +│ ├── assets/ +│ │ ├── bg-capture-48.png # 48×48 水印背景 +│ │ └── bg-capture-96.png # 96×96 水印背景 +│ └── app.js # UI 交互逻辑 +└── README.md +``` + +## 核心模块 + +### alphaMap.js + +从背景捕获图像计算 Alpha 通道: + +```javascript +export function calculateAlphaMap(bgCaptureImageData) { + // 提取 RGB 通道最大值并归一化到 [0, 1] + const alphaMap = new Float32Array(width * height); + for (let i = 0; i < alphaMap.length; i++) { + const maxChannel = Math.max(r, g, b); + alphaMap[i] = maxChannel / 255.0; + } + return alphaMap; +} +``` + +### blendModes.js + +实现反向 Alpha 混合算法: + +```javascript +export function removeWatermark(imageData, alphaMap, position) { + // 对每个像素应用公式:original = (watermarked - α × 255) / (1 - α) + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const alpha = Math.min(alphaMap[idx], MAX_ALPHA); + const original = (watermarked - alpha * 255) / (1.0 - alpha); + imageData.data[idx] = Math.max(0, Math.min(255, original)); + } + } +} +``` + +### watermarkEngine.js + +主引擎类,协调整个处理流程: + +```javascript +export class WatermarkEngine { + async removeWatermarkFromImage(image) { + // 1. 检测水印尺寸 + const config = detectWatermarkConfig(width, height); + + // 2. 获取 alpha map + const alphaMap = await this.getAlphaMap(config.logoSize); + + // 3. 移除水印 + removeWatermark(imageData, alphaMap, position); + + return canvas; + } +} +``` + +## 浏览器兼容性 + +- ✅ Chrome 90+ +- ✅ Firefox 88+ +- ✅ Safari 14+ +- ✅ Edge 90+ + +需要支持: +- ES6 Modules +- Canvas API +- Async/Await +- TypedArray (Float32Array, Uint8ClampedArray) + +--- + +## 局限性 + +- 只去除了 **Gemini 可见的水印**(即右下角的半透明 Logo) +- 无法去除隐形或隐写水印。[(了解更多关于 SynthID 的信息)](https://support.google.com/gemini/answer/16722517) +- 针对 Gemini 当前的水印模式设计(截至 2025 年) + +## 免责声明 + +本工具仅限**个人学习研究**所用,不得用于商业用途。 + +根据您所在的司法管辖区及图像的实际用途,移除水印的行为可能具有潜在的法律影响。用户需自行确保其使用行为符合适用法律、相关服务条款以及知识产权规定,并对此承担全部责任。 + +作者不纵容也不鼓励将本工具用于侵犯版权、虚假陈述或任何其他非法用途。 + +**本软件按“原样”提供,不提供任何形式(无论是明示或暗示)的保证。在任何情况下,作者均不对因使用本软件而产生的任何索赔、损害或其他责任承担任何义务。** + +## 许可证 + +[MIT License](./LICENSE) + +## 相关链接 + +- [Gemini Watermark Tool](https://github.com/allenk/GeminiWatermarkTool) +- [算法原理说明](https://allenkuo.medium.com/removing-gemini-ai-watermarks-a-deep-dive-into-reverse-alpha-blending-bbbd83af2a3f) + +## 致谢 + +基于 [Gemini Watermark Tool](https://github.com/allenk/GeminiWatermarkTool) C++ 版本移植 diff --git a/assets/bg_48.png b/assets/bg_48.png new file mode 100644 index 0000000..ed11f16 Binary files /dev/null and b/assets/bg_48.png differ diff --git a/assets/bg_96.png b/assets/bg_96.png new file mode 100644 index 0000000..6a73b58 Binary files /dev/null and b/assets/bg_96.png differ diff --git a/docs/1.webp b/docs/1.webp new file mode 100644 index 0000000..1c8a7ab Binary files /dev/null and b/docs/1.webp differ diff --git a/docs/2.webp b/docs/2.webp new file mode 100644 index 0000000..ce74bdd Binary files /dev/null and b/docs/2.webp differ diff --git a/docs/3.webp b/docs/3.webp new file mode 100644 index 0000000..da812f7 Binary files /dev/null and b/docs/3.webp differ diff --git a/docs/4.webp b/docs/4.webp new file mode 100644 index 0000000..381905c Binary files /dev/null and b/docs/4.webp differ diff --git a/docs/5.webp b/docs/5.webp new file mode 100644 index 0000000..2ba1888 Binary files /dev/null and b/docs/5.webp differ diff --git a/docs/unwatermarked_1.webp b/docs/unwatermarked_1.webp new file mode 100644 index 0000000..2118673 Binary files /dev/null and b/docs/unwatermarked_1.webp differ diff --git a/docs/unwatermarked_2.webp b/docs/unwatermarked_2.webp new file mode 100644 index 0000000..db1f4a3 Binary files /dev/null and b/docs/unwatermarked_2.webp differ diff --git a/docs/unwatermarked_3.webp b/docs/unwatermarked_3.webp new file mode 100644 index 0000000..dab4ffc Binary files /dev/null and b/docs/unwatermarked_3.webp differ diff --git a/docs/unwatermarked_4.webp b/docs/unwatermarked_4.webp new file mode 100644 index 0000000..17795ff Binary files /dev/null and b/docs/unwatermarked_4.webp differ diff --git a/docs/unwatermarked_5.webp b/docs/unwatermarked_5.webp new file mode 100644 index 0000000..296d77c Binary files /dev/null and b/docs/unwatermarked_5.webp differ diff --git a/i18n/en-US.json b/i18n/en-US.json new file mode 100644 index 0000000..dd97d59 --- /dev/null +++ b/i18n/en-US.json @@ -0,0 +1,42 @@ +{ + "title": "Gemini Watermark Remover - Lossless Watermark Removal Tool", + "header.title": "Gemini Watermark Remover", + "nav.principle": "How It Works?", + "main.title": "Gemini AI Watermark Removal", + "main.subtitle": "Based on reverse alpha blending algorithm, pure browser-side processing, Free, Fast, and Lossless", + "upload.text": "Click to select or drag images here", + "upload.hint": "Supports JPG, PNG, WebP", + "step.1": "Select Original Image", + "step.2": "Auto Processing", + "step.3": "Save Clean Image", + "preview.original": "Original Preview", + "preview.result": "Processed Result", + "panel.title": "Control Panel", + "btn.download": "Download", + "btn.reset": "Reset / Change Image", + "progress.text": "Progress", + "btn.downloadAll": "Download All", + "feature.title": "Features", + "feature.speed.title": "Lightning Fast", + "feature.speed.desc": "Based on modern browser technologies, millisecond response, no waiting queue", + "feature.privacy.title": "Privacy First", + "feature.privacy.desc": "Pure frontend processing, images never leave your device", + "feature.free.title": "Completely Free", + "feature.free.desc": "No usage limits, no hidden fees, ready to use", + "footer.desc": "Gemini Watermark Remover, for educational purposes only", + "footer.links": "Links", + "footer.terms": "Terms of Use", + "footer.tech": "Technology", + "footer.copyright": "© 2025 Gemini Watermark Remover. All rights reserved.", + "loading.text": "Processing...", + "status.loading": "Loading resources...", + "status.pending": "Pending...", + "status.processing": "Processing...", + "status.success": "Completed", + "status.failed": "Failed", + "info.size": "Size", + "info.watermark": "Detected Watermark", + "info.position": "Position", + "info.status": "Status", + "info.removed": "Watermark Removed" +} diff --git a/i18n/zh-CN.json b/i18n/zh-CN.json new file mode 100644 index 0000000..6a19104 --- /dev/null +++ b/i18n/zh-CN.json @@ -0,0 +1,42 @@ +{ + "title": "Gemini Watermark Remover - Gemini 无损去水印工具", + "header.title": "Gemini Watermark Remover", + "nav.principle": "去水印原理", + "main.title": "Gemini AI 图像去水印", + "main.subtitle": "基于反向 Alpha 混合算法,纯浏览器本地处理,免费、极速、无损", + "upload.text": "点击选择 或 拖拽图片至此", + "upload.hint": "支持 JPG, PNG, WebP", + "step.1": "选择原始图片", + "step.2": "算法自动解析", + "step.3": "保存无水印图", + "preview.original": "原图预览", + "preview.result": "处理结果", + "panel.title": "操作面板", + "btn.download": "下载结果", + "btn.reset": "重置 / 更换图片", + "progress.text": "处理进度", + "btn.downloadAll": "全部下载", + "feature.title": "功能特点", + "feature.speed.title": "极速处理", + "feature.speed.desc": "基于现代浏览器技术加速处理,毫秒级响应,无需等待排队", + "feature.privacy.title": "隐私安全", + "feature.privacy.desc": "纯前端运行,图片数据不离机,绝不上传服务器", + "feature.free.title": "完全免费", + "feature.free.desc": "无次数限制,无隐藏付费,即开即用", + "footer.desc": "Gemini 无损去水印工具,本工具仅供学习交流使用", + "footer.links": "链接", + "footer.terms": "使用条款", + "footer.tech": "技术", + "footer.copyright": "© 2025 Gemini Watermark Remover. All rights reserved.", + "loading.text": "正在处理...", + "status.loading": "正在加载资源...", + "status.pending": "等待处理...", + "status.processing": "处理中...", + "status.success": "处理完成", + "status.failed": "处理失败", + "info.size": "尺寸", + "info.watermark": "检测到的水印", + "info.position": "位置", + "info.status": "状态", + "info.removed": "水印已移除" +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..e5d3f5c --- /dev/null +++ b/index.html @@ -0,0 +1,270 @@ + + + + + + Gemini Watermark Remover + + + + + + + + + + + + + + +
+
+
+ + + +

+ Gemini Watermark Remover +

+
+ +
+
+ +
+
+
+ +

+ Gemini AI 图像去水印 +

+

+ 基于反向 Alpha 混合算法,纯浏览器本地处理,免费、极速、无损 +

+ +
+
+
+
+ +
+

点击选择 或 拖拽图片至此

+

支持 JPG, PNG, WebP (无文件大小限制)

+
+ +
+
+
+ +
+
+
+
1
+ 选择原始图片 +
+
+
2
+ 算法自动解析 +
+
+
3
+ 保存无水印图 +
+
+
+ + + + + +
+
+
+

功能特点

+
+
+
+
+ +
+

极速处理

+

基于现代浏览器技术加速处理,毫秒级响应,无需等待排队

+
+
+
+ +
+

隐私安全

+

纯前端运行,图片数据不离机,绝不上传服务器

+
+
+
+ + + +
+

完全免费

+

无次数限制,无隐藏付费,即开即用

+
+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/js/app.js b/js/app.js new file mode 100644 index 0000000..96ffe7a --- /dev/null +++ b/js/app.js @@ -0,0 +1,304 @@ +/** + * 主应用程序 - UI 交互逻辑 + */ + +import { WatermarkEngine } from './core/watermarkEngine.js'; +import i18n from './i18n.js'; + +// 全局状态 +let engine = null; +let imageQueue = []; +let processedCount = 0; + +// DOM 元素 +const uploadArea = document.getElementById('uploadArea'); +const fileInput = document.getElementById('fileInput'); +const singlePreview = document.getElementById('singlePreview'); +const multiPreview = document.getElementById('multiPreview'); +const imageList = document.getElementById('imageList'); +const progressText = document.getElementById('progressText'); +const downloadAllBtn = document.getElementById('downloadAllBtn'); +const loadingOverlay = document.getElementById('loadingOverlay'); +const originalCanvas = document.getElementById('originalCanvas'); +const processedSection = document.getElementById('processedSection'); +const processedImage = document.getElementById('processedImage'); +const originalInfo = document.getElementById('originalInfo'); +const processedInfo = document.getElementById('processedInfo'); +const downloadBtn = document.getElementById('downloadBtn'); +const resetBtn = document.getElementById('resetBtn'); +const statusMessage = document.getElementById('statusMessage'); + +/** + * 初始化应用 + */ +async function init() { + try { + await i18n.init(); + setupLanguageSwitch(); + showLoading(i18n.t('status.loading')); + + engine = await WatermarkEngine.create(); + + hideLoading(); + setupEventListeners(); + } catch (error) { + hideLoading(); + console.error('初始化错误:', error); + } +} + +/** + * 设置语言切换 + */ +function setupLanguageSwitch() { + const btn = document.getElementById('langSwitch'); + btn.textContent = i18n.locale === 'zh-CN' ? 'EN' : '中文'; + btn.addEventListener('click', async () => { + const newLocale = i18n.locale === 'zh-CN' ? 'en-US' : 'zh-CN'; + await i18n.switchLocale(newLocale); + btn.textContent = newLocale === 'zh-CN' ? 'EN' : '中文'; + updateDynamicTexts(); + }); +} + +/** + * 设置事件监听器 + */ +function setupEventListeners() { + uploadArea.addEventListener('click', () => fileInput.click()); + fileInput.addEventListener('change', handleFileSelect); + + uploadArea.addEventListener('dragover', (e) => { + e.preventDefault(); + uploadArea.classList.add('dragover'); + }); + + uploadArea.addEventListener('dragleave', () => { + uploadArea.classList.remove('dragover'); + }); + + uploadArea.addEventListener('drop', (e) => { + e.preventDefault(); + uploadArea.classList.remove('dragover'); + handleFiles(Array.from(e.dataTransfer.files)); + }); + + downloadAllBtn.addEventListener('click', downloadAll); + resetBtn.addEventListener('click', reset); +} + +function reset() { + singlePreview.style.display = 'none'; + multiPreview.style.display = 'none'; + imageQueue = []; + processedCount = 0; + fileInput.value = ''; +} + +function handleFileSelect(e) { + handleFiles(Array.from(e.target.files)); +} + +function handleFiles(files) { + const validFiles = files.filter(file => { + if (!file.type.match('image/(jpeg|png|webp)')) return false; + if (file.size > 20 * 1024 * 1024) return false; + return true; + }); + + if (validFiles.length === 0) return; + + imageQueue = validFiles.map((file, index) => ({ + id: Date.now() + index, + file, + name: file.name, + status: 'pending', + originalImg: null, + processedBlob: null + })); + + processedCount = 0; + + if (validFiles.length === 1) { + singlePreview.style.display = 'block'; + multiPreview.style.display = 'none'; + processSingle(imageQueue[0]); + } else { + singlePreview.style.display = 'none'; + multiPreview.style.display = 'block'; + imageList.innerHTML = ''; + updateProgress(); + multiPreview.scrollIntoView({ behavior: 'smooth', block: 'start' }); + imageQueue.forEach(item => createImageCard(item)); + processQueue(); + } +} + +async function processSingle(item) { + try { + const img = await loadImage(item.file); + item.originalImg = img; + + originalCanvas.width = img.width; + originalCanvas.height = img.height; + originalCanvas.getContext('2d').drawImage(img, 0, 0); + + // 显示图片信息 + const watermarkInfo = engine.getWatermarkInfo(img.width, img.height); + originalInfo.innerHTML = ` + ${i18n.t('info.size')}:${img.width} × ${img.height} px
+ ${i18n.t('info.watermark')}:${watermarkInfo.size}×${watermarkInfo.size} px
+ ${i18n.t('info.position')}:(${watermarkInfo.position.x}, ${watermarkInfo.position.y}) + `; + + const result = await engine.removeWatermarkFromImage(img); + const blob = await new Promise(resolve => result.toBlob(resolve, 'image/png')); + item.processedBlob = blob; + + processedImage.src = URL.createObjectURL(blob); + processedSection.style.display = 'block'; + downloadBtn.style.display = 'flex'; + downloadBtn.onclick = () => downloadImage(item); + + processedInfo.innerHTML = ` + ${i18n.t('info.size')}:${img.width} × ${img.height} px
+ ${i18n.t('info.status')}:${i18n.t('info.removed')} + `; + + processedSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } catch (error) { + console.error(error); + } +} + +function createImageCard(item) { + const card = document.createElement('div'); + card.id = `card-${item.id}`; + card.className = 'bg-white md:h-[130px] rounded-xl shadow-card border border-gray-100 overflow-hidden'; + card.innerHTML = ` +
+
+
+ +
+
+

${item.name}

+
${i18n.t('status.pending')}
+
+
+
+ +
+
+ `; + imageList.appendChild(card); +} + +async function processQueue() { + for (const item of imageQueue) { + const img = await loadImage(item.file); + item.originalImg = img; + document.getElementById(`result-${item.id}`).src = img.src; + } + + for (const item of imageQueue) { + if (item.status !== 'pending') continue; + + item.status = 'processing'; + updateStatus(item.id, i18n.t('status.processing')); + + try { + const result = await engine.removeWatermarkFromImage(item.originalImg); + const blob = await new Promise(resolve => result.toBlob(resolve, 'image/png')); + item.processedBlob = blob; + + document.getElementById(`result-${item.id}`).src = URL.createObjectURL(blob); + + item.status = 'completed'; + const watermarkInfo = engine.getWatermarkInfo(item.originalImg.width, item.originalImg.height); + updateStatus(item.id, `${i18n.t('info.size')}:${item.originalImg.width} × ${item.originalImg.height} px
+ ${i18n.t('info.watermark')}:${watermarkInfo.size}×${watermarkInfo.size} px
+ ${i18n.t('info.position')}:(${watermarkInfo.position.x}, ${watermarkInfo.position.y})`, true); + + const downloadBtn = document.getElementById(`download-${item.id}`); + downloadBtn.classList.remove('hidden'); + downloadBtn.onclick = () => downloadImage(item); + + processedCount++; + updateProgress(); + } catch (error) { + item.status = 'error'; + updateStatus(item.id, i18n.t('status.failed')); + console.error(error); + } + } + + if (processedCount > 0) { + downloadAllBtn.style.display = 'flex'; + } +} + +function loadImage(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = (e) => { + const img = new Image(); + img.onload = () => resolve(img); + img.onerror = reject; + img.src = e.target.result; + }; + reader.onerror = reject; + reader.readAsDataURL(file); + }); +} + +function updateStatus(id, text, isHtml = false) { + const el = document.getElementById(`status-${id}`); + if (el) el.innerHTML = isHtml ? text : text.replace(/\n/g, '
'); +} + +function updateProgress() { + progressText.textContent = `${i18n.t('progress.text')}: ${processedCount}/${imageQueue.length}`; +} + +function updateDynamicTexts() { + if (progressText.textContent) { + updateProgress(); + } +} + +function downloadImage(item) { + const a = document.createElement('a'); + a.href = URL.createObjectURL(item.processedBlob); + a.download = `unwatermarked_${item.name.replace(/\.[^.]+$/, '')}.png`; + a.click(); +} + +async function downloadAll() { + const completed = imageQueue.filter(item => item.status === 'completed'); + if (completed.length === 0) return; + + const zip = new JSZip(); + completed.forEach(item => { + const filename = `unwatermarked_${item.name.replace(/\.[^.]+$/, '')}.png`; + zip.file(filename, item.processedBlob); + }); + + const blob = await zip.generateAsync({ type: 'blob' }); + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = `unwatermarked_${Date.now()}.zip`; + a.click(); +} + +function showLoading(text = null) { + loadingOverlay.style.display = 'flex'; + const textEl = loadingOverlay.querySelector('p'); + if (textEl && text) textEl.textContent = text; +} + +function hideLoading() { + loadingOverlay.style.display = 'none'; +} + +init(); diff --git a/js/core/alphaMap.js b/js/core/alphaMap.js new file mode 100644 index 0000000..bc91557 --- /dev/null +++ b/js/core/alphaMap.js @@ -0,0 +1,30 @@ +/** + * Alpha Map 计算模块 + * 从背景捕获图像计算 alpha 通道 + */ + +/** + * 从背景捕获图像计算 alpha map + * @param {ImageData} bgCaptureImageData - 背景捕获的 ImageData 对象 + * @returns {Float32Array} Alpha map (值范围 0.0-1.0) + */ +export function calculateAlphaMap(bgCaptureImageData) { + const { width, height, data } = bgCaptureImageData; + const alphaMap = new Float32Array(width * height); + + // 对每个像素,取 RGB 三个通道的最大值并归一化 + for (let i = 0; i < alphaMap.length; i++) { + const idx = i * 4; // RGBA 格式,每个像素 4 个字节 + const r = data[idx]; + const g = data[idx + 1]; + const b = data[idx + 2]; + + // 取 RGB 最大值作为亮度值 + const maxChannel = Math.max(r, g, b); + + // 归一化到 [0, 1] 范围 + alphaMap[i] = maxChannel / 255.0; + } + + return alphaMap; +} diff --git a/js/core/blendModes.js b/js/core/blendModes.js new file mode 100644 index 0000000..0489b56 --- /dev/null +++ b/js/core/blendModes.js @@ -0,0 +1,61 @@ +/** + * 反向 Alpha 混合模块 + * 实现去除水印的核心算法 + */ + +// 常量定义 +const ALPHA_THRESHOLD = 0.002; // 忽略极小的 alpha 值(噪声) +const MAX_ALPHA = 0.99; // 避免除以接近零的值 +const LOGO_VALUE = 255; // 白色水印的颜色值 + +/** + * 使用反向 alpha 混合移除水印 + * + * 原理: + * Gemini 添加水印: watermarked = α × logo + (1 - α) × original + * 反向求解: original = (watermarked - α × logo) / (1 - α) + * + * @param {ImageData} imageData - 要处理的图像数据(会被原地修改) + * @param {Float32Array} alphaMap - Alpha 通道数据 + * @param {Object} position - 水印位置 {x, y, width, height} + */ +export function removeWatermark(imageData, alphaMap, position) { + const { x, y, width, height } = position; + + // 遍历水印区域的每个像素 + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + // 计算在原图中的索引(RGBA 格式,每个像素 4 个字节) + const imgIdx = ((y + row) * imageData.width + (x + col)) * 4; + + // 计算在 alpha map 中的索引 + const alphaIdx = row * width + col; + + // 获取 alpha 值 + let alpha = alphaMap[alphaIdx]; + + // 跳过极小的 alpha 值(噪声) + if (alpha < ALPHA_THRESHOLD) { + continue; + } + + // 限制 alpha 值,避免除零 + alpha = Math.min(alpha, MAX_ALPHA); + const oneMinusAlpha = 1.0 - alpha; + + // 对 RGB 三个通道应用反向 alpha 混合公式 + for (let c = 0; c < 3; c++) { + const watermarked = imageData.data[imgIdx + c]; + + // 反向 alpha 混合公式 + const original = (watermarked - alpha * LOGO_VALUE) / oneMinusAlpha; + + // 裁剪到 [0, 255] 范围 + imageData.data[imgIdx + c] = Math.max(0, Math.min(255, Math.round(original))); + } + + // Alpha 通道保持不变 + // imageData.data[imgIdx + 3] 不需要修改 + } + } +} diff --git a/js/core/watermarkEngine.js b/js/core/watermarkEngine.js new file mode 100644 index 0000000..6f827ae --- /dev/null +++ b/js/core/watermarkEngine.js @@ -0,0 +1,166 @@ +/** + * 水印引擎主模块 + * 协调水印检测、alpha map 计算和去除操作 + */ + +import { calculateAlphaMap } from './alphaMap.js'; +import { removeWatermark } from './blendModes.js'; + +const BG_48_PATH = '../assets/bg_48.png'; +const BG_96_PATH = '../assets/bg_96.png'; + +/** + * 根据图像尺寸检测水印配置 + * @param {number} imageWidth - 图像宽度 + * @param {number} imageHeight - 图像高度 + * @returns {Object} 水印配置 {logoSize, marginRight, marginBottom} + */ +export function detectWatermarkConfig(imageWidth, imageHeight) { + // Gemini 的水印规则: + // 如果图像宽高都大于 1024,使用 96×96 水印 + // 否则使用 48×48 水印 + if (imageWidth > 1024 && imageHeight > 1024) { + return { + logoSize: 96, + marginRight: 64, + marginBottom: 64 + }; + } else { + return { + logoSize: 48, + marginRight: 32, + marginBottom: 32 + }; + } +} + +/** + * 计算水印在图像中的位置 + * @param {number} imageWidth - 图像宽度 + * @param {number} imageHeight - 图像高度 + * @param {Object} config - 水印配置 + * @returns {Object} 水印位置 {x, y, width, height} + */ +export function calculateWatermarkPosition(imageWidth, imageHeight, config) { + const { logoSize, marginRight, marginBottom } = config; + + return { + x: imageWidth - marginRight - logoSize, + y: imageHeight - marginBottom - logoSize, + width: logoSize, + height: logoSize + }; +} + +/** + * 水印引擎类 + */ +export class WatermarkEngine { + constructor(bgCaptures) { + this.bgCaptures = bgCaptures; + this.alphaMaps = {}; + } + + static async create() { + const bg48 = new Image(); + const bg96 = new Image(); + + await Promise.all([ + new Promise((resolve, reject) => { + bg48.onload = resolve; + bg48.onerror = reject; + bg48.src = BG_48_PATH; + }), + new Promise((resolve, reject) => { + bg96.onload = resolve; + bg96.onerror = reject; + bg96.src = BG_96_PATH; + }) + ]); + + return new WatermarkEngine({ bg48, bg96 }); + } + + /** + * 从背景捕获图像获取 alpha map + * @param {number} size - 水印尺寸 (48 或 96) + * @returns {Promise} Alpha map + */ + async getAlphaMap(size) { + // 如果已缓存,直接返回 + if (this.alphaMaps[size]) { + return this.alphaMaps[size]; + } + + // 选择对应尺寸的背景捕获 + const bgImage = size === 48 ? this.bgCaptures.bg48 : this.bgCaptures.bg96; + + // 创建临时 canvas 来提取 ImageData + const canvas = document.createElement('canvas'); + canvas.width = size; + canvas.height = size; + const ctx = canvas.getContext('2d'); + ctx.drawImage(bgImage, 0, 0); + + const imageData = ctx.getImageData(0, 0, size, size); + + // 计算 alpha map + const alphaMap = calculateAlphaMap(imageData); + + // 缓存结果 + this.alphaMaps[size] = alphaMap; + + return alphaMap; + } + + /** + * 移除图像上的水印 + * @param {HTMLImageElement|HTMLCanvasElement} image - 输入图像 + * @returns {Promise} 处理后的 canvas + */ + async removeWatermarkFromImage(image) { + // 创建 canvas + const canvas = document.createElement('canvas'); + canvas.width = image.width; + canvas.height = image.height; + const ctx = canvas.getContext('2d'); + + // 绘制原图 + ctx.drawImage(image, 0, 0); + + // 获取图像数据 + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + + // 检测水印配置 + const config = detectWatermarkConfig(canvas.width, canvas.height); + const position = calculateWatermarkPosition(canvas.width, canvas.height, config); + + // 获取对应尺寸的 alpha map + const alphaMap = await this.getAlphaMap(config.logoSize); + + // 移除水印 + removeWatermark(imageData, alphaMap, position); + + // 将处理后的数据写回 canvas + ctx.putImageData(imageData, 0, 0); + + return canvas; + } + + /** + * 获取水印信息(用于显示) + * @param {number} imageWidth - 图像宽度 + * @param {number} imageHeight - 图像高度 + * @returns {Object} 水印信息 + */ + getWatermarkInfo(imageWidth, imageHeight) { + const config = detectWatermarkConfig(imageWidth, imageHeight); + const position = calculateWatermarkPosition(imageWidth, imageHeight, config); + + return { + size: config.logoSize, + position: position, + config: config + }; + } +} diff --git a/js/i18n.js b/js/i18n.js new file mode 100644 index 0000000..04688e8 --- /dev/null +++ b/js/i18n.js @@ -0,0 +1,41 @@ +const i18n = { + locale: localStorage.getItem('locale') || (navigator.language.startsWith('zh') ? 'zh-CN' : 'en-US'), + translations: {}, + + async init() { + await this.loadTranslations(this.locale); + this.applyTranslations(); + document.body.classList.remove('loading'); + }, + + async loadTranslations(locale) { + const res = await fetch(`/i18n/${locale}.json?_=${Date.now()}`); + this.translations = await res.json(); + this.locale = locale; + localStorage.setItem('locale', locale); + }, + + t(key) { + return this.translations[key] || key; + }, + + applyTranslations() { + document.documentElement.lang = this.locale; + document.title = this.t('title'); + document.querySelectorAll('[data-i18n]').forEach(el => { + const key = el.getAttribute('data-i18n'); + if (el.tagName === 'INPUT' && el.placeholder !== undefined) { + el.placeholder = this.t(key); + } else { + el.textContent = this.t(key); + } + }); + }, + + async switchLocale(locale) { + await this.loadTranslations(locale); + this.applyTranslations(); + } +}; + +export default i18n; diff --git a/terms.html b/terms.html new file mode 100644 index 0000000..efe78f2 --- /dev/null +++ b/terms.html @@ -0,0 +1,138 @@ + + + + + + Terms of Use - Gemini Watermark Remover + + + + + + + +
+
+
+ + + +

+ Gemini Watermark Remover +

+
+
+
+ +
+
+ +
+

Terms of Use

+

Last updated: December 19, 2025

+
+ +
+
+ +

Important Disclaimer

+
+ +
+
+
+

This tool is provided for personal and educational use.

+
+
+ +
+ +

A few things to keep in mind

+ +
+
+ +
+
+

Respect content policies.

+

The removal of watermarks may have legal or ethical implications depending on how you use the resulting images.

+
+
+ +
+
+ +
+
+

This tool removes visible watermarks only.

+

It does not affect any invisible/steganographic watermarks that may be embedded in the image data.

+ +

+ + Learn more about SynthID + + + + + + +

+
+
+
+
+ +
+
+

Privacy Policy

+

+ This service provides browser-based image processing. All processing is done locally on your device. We do not upload your images to any remote servers. +

+
+ +
+

Limitation of Liability

+

+ The tool is provided "as is". The developer shall not be liable for any data loss, image corruption, or legal disputes arising from the use of this tool. +

+
+
+ + + +
+
+ +
+
+
+

© 2025 Gemini Watermark Remover. All rights reserved.

+
+
+
+ + + \ No newline at end of file diff --git a/test.html b/test.html new file mode 100644 index 0000000..bb342db --- /dev/null +++ b/test.html @@ -0,0 +1,196 @@ + + + + + + 算法测试 - Gemini 去水印工具 + + + +

Gemini 去水印工具 - 算法测试

+ +
+

测试 1: Alpha Map 计算

+
+ +
+ +
+

测试 2: 水印检测

+
+
+ +
+

测试 3: 反向 Alpha 混合

+
+ + +
+ + + +