IOS基于LSB图片水印方案

该方案在Xcode构建过程中对App Bundle内的PNG图片资源进行最低有效位(LSB)嵌入,将水印信息隐藏于像素数据中,不影响视觉效果且可追踪版权,当然其他的作用,自己进行体会。


一、方案核心

  • 水印算法:LSB(最低有效位)隐写术,将水印二进制串嵌入图片每个像素RGB分量的最低位。人眼无法察觉,满足"隐式增加"。
  • 处理时机 :在Copy Bundle Resources阶段之后,对已拷贝到应用包中的图片进行原地修改,不污染源文件。
  • 开关控制 :通过Xcode Build Settings中的自定义变量ENABLE_INVISIBLE_WATERMARK控制是否执行,默认关闭。
  • 适用格式 :PNG(无损压缩,LSB稳定),可扩展支持BMP。不处理JPEG(有损压缩会破坏水印)。

二、实现代码

2.1 Python脚本 invisible_watermark.py

将此脚本放入项目根目录的Scripts文件夹中。

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys
import struct
from PIL import Image

# ================== 配置 ==================
# 水印内容(建议使用项目标识+构建时间,保证唯一性)
WATERMARK_TEXT = "COPYRIGHT_YOURAPP_2026"
# 水印起始标记(用于定位,避免误读)
START_MARKER = 0xAA  # 10101010
END_MARKER = 0x55     # 01010101
# ==========================================

def text_to_bits(text):
    """将字符串转为二进制位列表(含起始/结束标记)"""
    # 添加起始标记(8位)
    bits = [int(b) for b in format(START_MARKER, '08b')]
    # 添加文本长度(32位,大端)
    text_bytes = text.encode('utf-8')
    length_bits = []
    for byte in struct.pack('>I', len(text_bytes)):
        length_bits.extend([int(b) for b in format(byte, '08b')])
    bits.extend(length_bits)
    # 添加文本内容
    for byte in text_bytes:
        bits.extend([int(b) for b in format(byte, '08b')])
    # 添加结束标记(8位)
    bits.extend([int(b) for b in format(END_MARKER, '08b')])
    return bits

def embed_lsb(image_path, bits):
    """将二进制位嵌入图片的RGB最低有效位,返回是否成功"""
    img = Image.open(image_path).convert('RGB')
    pixels = img.load()
    width, height = img.size
    
    total_bits = width * height * 3  # 每个像素3个通道
    if len(bits) > total_bits:
        print(f"  错误: 图片容量不足,需要{len(bits)}位,实际{total_bits}位")
        return False
    
    idx = 0
    for y in range(height):
        for x in range(width):
            r, g, b = pixels[x, y]
            if idx < len(bits):
                r = (r & 0xFE) | bits[idx]
                idx += 1
            if idx < len(bits):
                g = (g & 0xFE) | bits[idx]
                idx += 1
            if idx < len(bits):
                b = (b & 0xFE) | bits[idx]
                idx += 1
            pixels[x, y] = (r, g, b)
            if idx >= len(bits):
                break
        if idx >= len(bits):
            break
    
    img.save(image_path, format='PNG', optimize=False)
    return True

def process_bundle(bundle_path):
    """遍历App Bundle中的PNG图片,嵌入水印"""
    processed = 0
    for root, dirs, files in os.walk(bundle_path):
        for file in files:
            if file.lower().endswith('.png'):
                file_path = os.path.join(root, file)
                try:
                    bits = text_to_bits(WATERMARK_TEXT)
                    if embed_lsb(file_path, bits):
                        print(f"  ✓ 已处理: {os.path.relpath(file_path, bundle_path)}")
                        processed += 1
                    else:
                        print(f"  ✗ 容量不足,跳过: {file_path}")
                except Exception as e:
                    print(f"  ✗ 处理失败 {file_path}: {e}")
    return processed

def main():
    if len(sys.argv) < 2:
        print("用法: python3 invisible_watermark.py <App Bundle路径>")
        sys.exit(1)
    
    bundle_path = sys.argv[1]
    if not os.path.isdir(bundle_path):
        print(f"错误: 路径不存在或不是目录: {bundle_path}")
        sys.exit(1)
    
    print(f"开始处理Bundle: {bundle_path}")
    print(f"水印内容: {WATERMARK_TEXT}")
    count = process_bundle(bundle_path)
    print(f"完成,共处理 {count} 个PNG图片")

if __name__ == '__main__':
    main()

2.2 Xcode Run Script 集成

在Xcode项目Target的Build Phases中添加一个新的Run Script Phase,放在Copy Bundle Resources之后,确保资源已拷贝到App Bundle。

脚本内容:

bash 复制代码
# 不可见水印开关:仅在配置了ENABLE_INVISIBLE_WATERMARK且值为YES时执行
if [ "${ENABLE_INVISIBLE_WATERMARK}" != "YES" ]; then
    echo "🔇 不可见水印已禁用 (ENABLE_INVISIBLE_WATERMARK != YES)"
    exit 0
fi

# 检查Python3环境
if ! command -v python3 &> /dev/null; then
    echo "⚠️ 未找到python3,跳过水印处理"
    exit 0
fi

# 检查Pillow库,若未安装则自动安装(可选)
python3 -c "import PIL" 2>/dev/null
if [ $? -ne 0 ]; then
    echo "📦 安装Pillow库..."
    pip3 install Pillow --user --quiet
fi

# 获取App Bundle路径(针对模拟器和真机统一处理)
APP_PATH="${TARGET_BUILD_DIR}/${EXECUTABLE_FOLDER_PATH}"
if [ ! -d "$APP_PATH" ]; then
    echo "❌ 未找到App Bundle: $APP_PATH"
    exit 1
fi

# 脚本所在路径(假设放在项目根目录/Scripts/下)
SCRIPT_PATH="${SRCROOT}/Scripts/invisible_watermark.py"

if [ ! -f "$SCRIPT_PATH" ]; then
    echo "❌ 未找到水印脚本: $SCRIPT_PATH"
    exit 1
fi

echo "🔏 开始嵌入不可见水印..."
python3 "$SCRIPT_PATH" "$APP_PATH"
echo "✅ 水印嵌入完成"

2.3 开关配置方式

在Xcode项目的Build Settings中添加User-Defined Setting:

  1. 选择Target → Build Settings
  2. 点击+ → Add User-Defined Setting
  3. 设置Key为ENABLE_INVISIBLE_WATERMARK
  4. 设置Value为YES(启用)或NO(禁用)

建议不同配置使用不同值:

  • Debug:NO(加快构建)
  • Release:YES(正式包加水印)

通过Build Configuration下的ENABLE_INVISIBLE_WATERMARK分别设置即可。


三、验证水印存在性(可选,用于追踪)

如果需要从图片中提取水印以验证版权,可提供以下提取脚本(单独使用,不在构建时执行):

python 复制代码
#!/usr/bin/env python3
import sys
from PIL import Image

def extract_lsb(image_path):
    img = Image.open(image_path).convert('RGB')
    pixels = img.load()
    width, height = img.size
    
    bits = []
    for y in range(height):
        for x in range(width):
            r, g, b = pixels[x, y]
            bits.append(r & 1)
            bits.append(g & 1)
            bits.append(b & 1)
    
    # 查找起始标记
    start_marker_bits = [int(b) for b in format(0xAA, '08b')]
    for i in range(len(bits) - 8):
        if bits[i:i+8] == start_marker_bits:
            # 读取长度
            length_bits = bits[i+8:i+8+32]
            length = 0
            for j, bit in enumerate(length_bits):
                length |= (bit << (31 - j))
            # 读取文本
            text_bits = bits[i+8+32:i+8+32+length*8]
            text_bytes = bytearray()
            for j in range(0, len(text_bits), 8):
                byte = 0
                for k in range(8):
                    byte |= (text_bits[j+k] << (7 - k))
                text_bytes.append(byte)
            # 验证结束标记
            end_pos = i+8+32+length*8
            end_marker_bits = [int(b) for b in format(0x55, '08b')]
            if bits[end_pos:end_pos+8] == end_marker_bits:
                print(f"提取水印: {text_bytes.decode('utf-8')}")
                return
    print("未检测到水印")

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("用法: python3 extract_watermark.py <image.png>")
    else:
        extract_lsb(sys.argv[1])

四、方案优势

特性 说明
无感性 LSB水印肉眼完全不可见,不改变图片观感
源文件安全 只修改构建产物(DerivedData中的App Bundle),不触碰.xcassets原始图片
开关可控 通过Build Settings一键启用/禁用,不同配置灵活切换
自动化 集成到Xcode构建流程,无需手动操作
可追溯 支持从图片中提取水印,用于版权验证
轻量 仅依赖Python3 + Pillow,macOS自带环境,自动安装缺失库

五、注意事项

  1. 仅支持PNG:JPEG等有损压缩会破坏LSB水印,脚本会自动跳过非PNG文件。
  2. 性能影响:嵌入水印会增加构建时间(取决于图片数量和大小),建议仅在Release模式下启用。
  3. 图片容量 :水印文本长度+标记位约需(8+32+文本长度*8+8)位。一张512x512的PNG可容纳约786432位(约96KB文本),完全满足需求。
  4. App Store合规:该水印不影响App功能,也未注入额外代码,符合App Store审核标准。

六、集成步骤

  1. 将invisible_watermark.py放入项目Scripts/文件夹。
  2. 在Xcode Build Phases中添加Run Script,粘贴上述脚本内容。
  3. 在Build Settings中添加User-Defined Setting ENABLE_INVISIBLE_WATERMARK,Release设为YES。
  4. 正常构建,水印自动嵌入。

此方案已用于多个生产项目,稳定可靠。

相关推荐
茶底世界之下4 小时前
类型擦除之后,Metal 滤镜组合为什么不能只执行一个 Filter?
ios·swift
传奇开心果编程6 小时前
【SwiftUI提高练中学】第8课 网络层架构:拦截器、重试、缓存与离线优先
学习·macos·ui·ios·swiftui
传奇开心果编程13 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
tink15 小时前
告别 Xcode IDE:用 VS Code + SweetPad + XcodeGen 开发 iOS 应用的完整指南
ios·swiftui·xcode
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程17 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程17 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
黑科技iOS上架17 小时前
iOS隐私合规扫描工具
ios·审核
传奇开心果编程18 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
传奇开心果编程18 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer