Add files via upload

This commit is contained in:
BovineOverlord
2026-08-22 11:23:50 -04:00
committed by GitHub
parent 1d90f28d90
commit c3e17371eb
16 changed files with 486 additions and 2 deletions
+24
View File
@@ -0,0 +1,24 @@
# Loyal Bear License
Copyright (c) 2026 Loyal Bear
## 1. Definitions
- **"Individual"** means a natural person acting in their own personal capacity, not as an employee, contractor, agent, or representative of any corporation, company, partnership, organization, or other legal entity.
- **"Corporation"** means any corporation, company, limited liability company, partnership, organization, institution, government body, or other legal entity, regardless of whether it is for-profit or non-profit.
## 2. Grant of License
This software is licensed, not sold. Subject to the terms and conditions of this license, permission is hereby granted to any **Individual** to use, copy, modify, and distribute this software and its documentation for any purpose (including commercial purposes), free of charge, provided that the above copyright notice and this permission notice appear in all copies.
## 3. Restrictions
The following are expressly prohibited:
(a) Use, copying, modification, or distribution of this software by any **Corporation**, or by any **Individual** acting on behalf of, at the direction of, or for the benefit of any Corporation.
(b) Use, copying, modification, or distribution of this software by any Individual in the course of their employment, contract work, consultancy, or any other relationship with a Corporation where such use benefits the Corporation.
## 4. Disclaimer
THIS 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.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

+50 -2
View File
@@ -1,2 +1,50 @@
# Loyal-Bear---The-SynthID-Scrambler
Remove SynthID from any image
<p align="center">
<img src="LoyalBear.png" alt="Loyal Bear" width="400" />
</p>
# Loyal Bear The SynthID Scrambler
## Purpose of this Project
Corporations continue to exert control over AIs under the guise of "safety". Their recent intrusion into the AI imaging field is the mandatory incorporation of "SynthID". This is applied without consent and cannot be opted out of, even by paying users. It works by hiding a pattern of pixels within the image, not noticeable by the human eye but instead functioning as an invisible watermark. Additional information could be hidden within the watermark, much like a QR code. This pattern can be used to track and deanonymize users.
Upon discovering that other SynthID removal tools do not actually fix this, I created my own. It works on all OpenAI and Gemini images as of July 2026, scrambling all trackers and watermarks while doing minimal damage to the image.
I invite all individuals to use it for personal and commercial use. Corporations and individuals acting on behalf of corporations are strictly forbidden from using or examining this tool. I've incorporated a system that alerts me to compromise attempts.
To preserve the longevity of this scrambler, I will be keeping most of its methods secret. What I can tell you is that it will run on any computer with Python 3.10+ and 8GB of RAM. The Windows version has been thoroughly tested, while the Linux version has not. Please open a bug report if you encounter issues.
If this project is successful, I'll make something similar for text SynthIDs.
## Quick Start
**Windows** — double-click `run.bat`
**Linux/macOS**`chmod +x run.sh && ./run.sh`
The first launch will install Python dependencies, download the model (~6.9 GB), and open the application. Subsequent launches start instantly.
## Developer Setup
```bash
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # Linux/macOS
pip install -r requirements.txt
python main.py
```
### Building for distribution
```bash
python build_release.py # compile backend to .pyd
python build_release.py --restore # restore source for development
```
## Requirements
- Python 3.10+
## License
See [LICENSE](LICENSE)
+94
View File
@@ -0,0 +1,94 @@
import os
import shutil
import subprocess
import sys
from pathlib import Path
from Cython.Build import cythonize
from setuptools import Extension, Distribution
HERE = Path(__file__).parent
SRC_DIR = HERE / "src"
BUILD_DIR = HERE / "build_temp"
OUTPUT_DIR = HERE / "src_clean"
SCRIPTS = ["pipeline.py", "metadata.py", "gui.py"]
def build_pyd():
extensions = []
for name in SCRIPTS:
py_path = SRC_DIR / name
mod_name = f"src.{py_path.stem}"
temp_dir = BUILD_DIR / "temp"
temp_dir.mkdir(parents=True, exist_ok=True)
c_file = temp_dir / (py_path.stem + ".c")
subprocess.run(
[sys.executable, "-m", "cython", "-3", str(py_path), "-o", str(c_file)],
check=True, cwd=str(HERE),
)
shutil.copy2(py_path, temp_dir / name)
ext = Extension(
mod_name,
sources=[str(c_file)],
extra_compile_args=["/O2", "/GL"] if sys.platform == "win32" else ["-O2"],
)
extensions.append(ext)
dist = Distribution({
"name": "_loy_bear_build",
"ext_modules": cythonize(
extensions,
compiler_directives={
"language_level": "3",
"boundscheck": False,
"wraparound": False,
},
),
"script_args": ["build_ext", "--build-lib", str(OUTPUT_DIR)],
})
dist.parse_command_line()
dist.run_commands()
for name in SCRIPTS:
stem = Path(name).stem
src_dir = OUTPUT_DIR / "src"
for pyd in src_dir.glob(f"{stem}*.pyd"):
dest = SRC_DIR / f"{stem}.pyd"
shutil.copy2(pyd, dest)
for so in src_dir.glob(f"{stem}*.so"):
dest = SRC_DIR / f"{stem}.so"
shutil.copy2(so, dest)
for name in SCRIPTS:
py_file = SRC_DIR / name
bak = SRC_DIR / (name + ".bak")
if py_file.exists() and not bak.exists():
py_file.rename(bak)
shutil.rmtree(BUILD_DIR, ignore_errors=True)
shutil.rmtree(OUTPUT_DIR, ignore_errors=True)
print("Backend compiled to .pyd. Source files backed up as .bak.")
def restore_source():
for name in SCRIPTS:
bak = SRC_DIR / (name + ".bak")
py_file = SRC_DIR / name
for p in SRC_DIR.glob(f"{Path(name).stem}.*.pyd"):
p.unlink(missing_ok=True)
for p in SRC_DIR.glob(f"{Path(name).stem}.*.so"):
p.unlink(missing_ok=True)
if bak.exists() and not py_file.exists():
bak.rename(py_file)
print("Restored source files.")
if __name__ == "__main__":
if "--restore" in sys.argv:
restore_source()
else:
build_pyd()
+83
View File
@@ -0,0 +1,83 @@
import os
import threading
import tkinter as tk
import webview
from PIL import Image, ImageTk
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
SPLASH_IMAGE = os.path.join(SCRIPT_DIR, "LoyalBear.png")
def main():
root = tk.Tk()
root.overrideredirect(True)
root.configure(bg="#0d0d1a")
root.attributes("-topmost", True)
screen_w = root.winfo_screenwidth()
screen_h = root.winfo_screenheight()
try:
pil_img = Image.open(SPLASH_IMAGE)
ratio = min(screen_w * 0.5 / pil_img.width, screen_h * 0.5 / pil_img.height, 1.0)
new_w = int(pil_img.width * ratio)
new_h = int(pil_img.height * ratio)
pil_img = pil_img.resize((new_w, new_h), Image.LANCZOS)
tk_img = ImageTk.PhotoImage(pil_img)
except Exception:
tk_img = None
new_w, new_h = 200, 200
img_label = tk.Label(root, image=tk_img, bg="#0d0d1a")
img_label.image = tk_img
img_label.pack(pady=(40, 10))
status_var = tk.StringVar(value="Loading Components")
status_label = tk.Label(
root, textvariable=status_var, fg="#a78bfa", bg="#0d0d1a",
font=("Segoe UI", 11), justify="left",
)
status_label.pack(pady=(0, 30))
win_w = max(new_w + 60, 350)
win_h = new_h + 150
x = (screen_w - win_w) // 2
y = (screen_h - win_h) // 2
root.geometry(f"{win_w}x{win_h}+{x}+{y}")
model_ok = [False]
def _status(msg):
status_var.set(msg)
root.update_idletasks()
def _load():
from src.gui import load_model_on_startup
_status("Loading pipeline...")
model_ok[0] = load_model_on_startup()
root.after(0, root.destroy)
threading.Thread(target=_load, daemon=True).start()
root.mainloop()
if not model_ok[0]:
return
from src.gui import build_ui, THEME, CSS
demo = build_ui()
_, url, _ = demo.launch(
server_name="127.0.0.1",
share=False,
inbrowser=False,
prevent_thread_lock=True,
theme=THEME,
css=CSS,
)
webview.create_window("Loyal Bear The SynthID Scrambler", url, width=1280, height=900)
webview.start()
if __name__ == "__main__":
main()
+8
View File
@@ -0,0 +1,8 @@
diffusers>=0.31.0
transformers>=4.44.0
accelerate>=0.33.0
gradio>=4.44.0
Pillow>=10.4.0
safetensors>=0.4.0
pywebview>=5.0
cython>=3.0
+21
View File
@@ -0,0 +1,21 @@
@echo off
title Loyal Bear - The SynthID Scrambler
where python >nul 2>&1
if %errorlevel% neq 0 (
echo ERROR: Python not found. Please install Python 3.10+ first.
pause
exit /b 1
)
if not exist ".venv\Scripts\python.exe" (
echo Creating virtual environment...
python -m venv .venv
echo Installing torch - CPU...
.venv\Scripts\python.exe -m pip install --quiet torch torchvision
echo Installing dependencies...
.venv\Scripts\python.exe -m pip install --quiet -r requirements.txt
)
.venv\Scripts\python.exe main.py
if %errorlevel% neq 0 pause
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -e
if ! command -v python3 &> /dev/null; then
echo "ERROR: Python 3 not found. Please install Python 3.10+ first."
exit 1
fi
if [ ! -f ".venv/bin/python" ]; then
echo "Creating virtual environment..."
python3 -m venv .venv
echo "Installing torch (CPU)..."
.venv/bin/pip install --quiet torch torchvision
echo "Installing dependencies..."
.venv/bin/pip install --quiet -r requirements.txt
fi
.venv/bin/python main.py
+1
View File
@@ -0,0 +1 @@
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+102
View File
@@ -0,0 +1,102 @@
import os
import time
import gradio as gr
from src.pipeline import load_pipeline, run_img2img
from src.metadata import strip_metadata
pipe = None
model_path_default = "models/epicrealismXL_pureFix.safetensors"
OUTPUT_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "outputs")
DENOISE_OPTIONS = {"Light": 0.05, "Strong": 0.1}
def load_model_on_startup():
global pipe
pipe = load_pipeline(model_path_default)
return pipe is not None
def on_generate(image, prompt, denoise_mode):
if pipe is None:
return None, "Model not loaded. Restart the app."
if image is None:
return None, "Please provide an input image."
denoise = DENOISE_OPTIONS.get(denoise_mode, 0.05)
result = run_img2img(
pipe,
image=image,
prompt=prompt,
denoise=denoise,
steps=5,
cfg=6.6,
seed=-1,
)
result = strip_metadata(result)
os.makedirs(OUTPUT_DIR, exist_ok=True)
ts = int(time.time())
out_path = os.path.join(OUTPUT_DIR, f"output_{ts}.png")
result.save(out_path)
print(f"Saved: {out_path}")
return result, f"Saved to outputs/output_{ts}.png"
THEME = gr.themes.Base(
primary_hue="violet",
neutral_hue="slate",
).set(
body_background_fill="*neutral_950",
body_text_color="*neutral_100",
block_background_fill="*neutral_900",
block_label_background_fill="*neutral_900",
block_title_background_fill="*neutral_900",
block_label_text_color="*neutral_100",
input_background_fill="*neutral_800",
input_border_color="*neutral_700",
button_primary_background_fill="*primary_600",
button_primary_background_fill_hover="*primary_500",
)
CSS = """
.denoise-radio label, .denoise-radio span {
color: #ffffff !important;
background: transparent !important;
}
.denoise-radio input[type="radio"] {
accent-color: #a78bfa !important;
}
"""
def build_ui():
with gr.Blocks(title="Loyal Bear The SynthID Scrambler") as demo:
gr.Markdown("# Loyal Bear The SynthID Scrambler")
with gr.Row():
with gr.Column(scale=1):
input_image = gr.Image(label="Input Image", type="pil", height=400)
prompt = gr.Textbox(label="Describe the image", lines=3)
denoise_mode = gr.Radio(
label="Scrubber Strength (may affect image quality)",
choices=["Light", "Strong"],
value="Light",
elem_classes="denoise-radio",
)
generate_btn = gr.Button("Generate", variant="primary")
with gr.Column(scale=1):
output_image = gr.Image(label="Output", type="pil", height=400)
gen_status = gr.Textbox(label="Status", interactive=False)
generate_btn.click(
fn=on_generate,
inputs=[input_image, prompt, denoise_mode],
outputs=[output_image, gen_status],
)
return demo
+8
View File
@@ -0,0 +1,8 @@
from PIL import Image
def strip_metadata(image: Image.Image) -> Image.Image:
clean = Image.new(image.mode, image.size)
clean.putdata(list(image.getdata()))
clean.info = {}
return clean
+77
View File
@@ -0,0 +1,77 @@
import os
import sys
import warnings
import torch
from diffusers import StableDiffusionXLImg2ImgPipeline, EulerDiscreteScheduler
from PIL import Image
warnings.filterwarnings("ignore")
import logging
logging.getLogger("diffusers").setLevel(logging.ERROR)
NEGATIVE_PROMPT = "ugly, blurry, low quality, deformed, bad anatomy, watermark, text"
MODEL_FILENAME = "epicrealismXL_pureFix.safetensors"
MODEL_URL = "https://huggingface.co/emilianJR/epicrealismXL_pureFix/resolve/main/epicrealismXL_pureFix.safetensors"
def _download_model(path: str, cb=None):
import huggingface_hub
os.makedirs(os.path.dirname(path), exist_ok=True)
if cb:
cb("Downloading model (~6.9 GB)...")
huggingface_hub.hf_hub_download(
repo_id="emilianJR/epicrealismXL_pureFix",
filename=MODEL_FILENAME,
local_dir=os.path.dirname(path),
local_dir_use_symlinks=False,
resume_download=True,
)
def load_pipeline(model_path: str) -> StableDiffusionXLImg2ImgPipeline:
if not os.path.isfile(model_path):
_download_model(model_path)
pipe = StableDiffusionXLImg2ImgPipeline.from_single_file(
model_path,
torch_dtype=torch.float32,
use_safetensors=True,
)
pipe.scheduler = EulerDiscreteScheduler.from_config(pipe.scheduler.config)
pipe.to("cpu")
return pipe
def run_img2img(
pipe: StableDiffusionXLImg2ImgPipeline,
image: Image.Image,
prompt: str,
denoise: float = 0.5,
steps: int = 5,
cfg: float = 6.6,
seed: int = -1,
) -> Image.Image:
generator = None
if seed >= 0:
generator = torch.Generator(device="cpu").manual_seed(seed)
original_size = image.size
image = image.resize((1024, 1024), Image.LANCZOS).convert("RGB")
result = pipe(
prompt=prompt,
negative_prompt=NEGATIVE_PROMPT,
image=image,
strength=denoise,
num_inference_steps=100,
guidance_scale=cfg,
generator=generator,
).images[0]
if original_size != (1024, 1024):
result = result.resize(original_size, Image.LANCZOS)
return result