mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-10-21 16:21:42 +02:00
Apply fixes from a full-repo review (code, tests, docs). Security / correctness: - Clamp attacker-controlled PNG/caBX chunk lengths to the remaining file size in metadata.py and noai/c2pa.py (a malformed length no longer drives a multi-GB read); skipped chunks seek instead of read. - noai/isobmff.strip_c2pa_boxes is now fail-safe on a malformed box: return the original bytes with a warning instead of silently truncating the tail, so metadata --remove can no longer emit a corrupt file. - doubao_engine._fixed_alpha_map clamps the glyph box to the image (no crash on degenerate width-vs-height). - watermark_remover._run_region_hires gates the phaseCorrelate offset on response and magnitude (a spurious shift no longer garbles text) and drops the generator after a CPU fallback (no MPS/CPU device mismatch). Robustness: - gemini_engine, doubao_engine, region_eraser normalize grayscale and RGBA inputs to BGR at the engine entry points. - image_io.imwrite returns False on an unwritable path (matches cv2). - invisible_engine guards a None imread result before use. - trustmark_detector._decoder uses a double-checked threading lock. - ctrlregen.tiling.tile_positions raises on overlap >= tile. - humanizer chromatic shift no longer wraps opposite-edge pixels. - identify OpenAI caveat keyed on the normalized vendor, not a substring. - Remove the dead "visible --detect-threshold" CLI option. - publish.yml verifies the release tag matches the package version. Docs: - README strength 0.05 to 0.10; .env.example HF_TOKEN marked optional; doubao_capture README updated to reverse-alpha-only; CLAUDE.md synced with the new behaviors and the batch command. Tests: new test_security_clamp.py for the read clamp and isobmff fail-safe; erase CLI coverage; integrity-clash rule 2 end-to-end; multi-tag EXIF survival and cross-format strip guards; channel/size, tiling, humanizer, and imwrite regressions. Full suite 493 passed, 2 skipped; ruff and pyright src/ clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
59 lines
2.7 KiB
Python
59 lines
2.7 KiB
Python
"""Analog Humanizer: film grain and chromatic aberration injection.
|
|
|
|
Simulates analog film imperfections to defeat digital AI perfection
|
|
classifiers. Ported from NeuralBleach.
|
|
"""
|
|
|
|
# cv2/numpy boundary: third-party libs ship no usable element types; relax the
|
|
# unknown-type rules for this file only.
|
|
# pyright: reportUnknownMemberType=false, reportUnknownArgumentType=false, reportUnknownVariableType=false, reportUnknownParameterType=false, reportMissingTypeArgument=false, reportMissingTypeStubs=false, reportMissingImports=false, reportArgumentType=false, reportAssignmentType=false, reportReturnType=false, reportCallIssue=false, reportIndexIssue=false, reportOperatorIssue=false, reportOptionalMemberAccess=false, reportOptionalCall=false, reportOptionalSubscript=false, reportOptionalOperand=false, reportAttributeAccessIssue=false, reportPrivateImportUsage=false, reportPrivateUsage=false, reportInvalidTypeForm=false, reportConstantRedefinition=false, reportUnnecessaryComparison=false
|
|
import cv2
|
|
import numpy as np
|
|
from numpy.typing import NDArray
|
|
|
|
|
|
def apply_analog_humanizer(image: NDArray, grain_intensity: float = 4.0, chromatic_shift: int = 1) -> NDArray:
|
|
"""
|
|
Apply Analog Humanizer (film grain and chromatic aberration) to an image.
|
|
This simulates analog film imperfections to defeat digital AI perfection classifiers.
|
|
|
|
Ported from NeuralBleach.
|
|
|
|
Args:
|
|
image: BGR image as numpy array (uint8).
|
|
grain_intensity: Standard deviation of the Gaussian noise (film grain).
|
|
chromatic_shift: Number of pixels to shift the red/blue color channels.
|
|
|
|
Returns:
|
|
Humanized BGR image.
|
|
"""
|
|
# Ensure image is BGR
|
|
if len(image.shape) != 3 or image.shape[2] != 3:
|
|
return image.copy()
|
|
|
|
# Split channels (OpenCV uses BGR)
|
|
# B = 0, G = 1, R = 2
|
|
b, g, r = cv2.split(image)
|
|
|
|
# 1. Chromatic Aberration
|
|
# Shift R channel left, B channel right. np.roll is circular, so it wraps
|
|
# the opposite edge into a thin colored fringe at the L/R borders; replicate
|
|
# the original edge columns there to keep the intended offset interior-only.
|
|
if chromatic_shift > 0:
|
|
r = np.roll(r, -chromatic_shift, axis=1)
|
|
r[:, -chromatic_shift:] = r[:, -chromatic_shift - 1 : -chromatic_shift]
|
|
b = np.roll(b, chromatic_shift, axis=1)
|
|
b[:, :chromatic_shift] = b[:, chromatic_shift : chromatic_shift + 1]
|
|
|
|
merged = cv2.merge((b, g, r))
|
|
|
|
# 2. Film Grain (Gaussian Noise)
|
|
if grain_intensity > 0:
|
|
img_f = merged.astype(np.float32)
|
|
noise = np.random.normal(0, grain_intensity, img_f.shape).astype(np.float32)
|
|
humanized = np.clip(img_f + noise, 0, 255).astype(np.uint8)
|
|
else:
|
|
humanized = merged
|
|
|
|
return humanized
|