Sign in

    moon-watermark

    Image watermark library for MoonBit: visible and invisible traceable watermarks with embed/extract/verify APIs and a CLI.

    watermark
    image
    security
    anti-piracy
    steganography
    Download zip
    Author
    Version
    0.1.2
    License
    Apache-2.0
    Last updated
    19 days ago
    Downloads
    43

    Dependencies

    #moon-watermark

    MoonBit 图像水印算法库:可见水印(文本 / 随机点阵 / Logo / 平铺)+ 不可见溯源水印(LSB),提供 embed / extract / verify / trace API 与配置 JSON 序列化。定位为通用图像水印基础库——电商防搬运、素材预览防盗、图片版权确权、内部泄密溯源、API 图片追踪、直播防去重等场景开箱即用。

    #功能矩阵

    #可见水印(已实现)

    类型API说明
    文本水印RgbaImage::embed_text + TextConfig内置 5×7 点阵字体(A-Z / 0-9 / 常用符号),支持缩放、透明度、定位
    随机点阵水印RgbaImage::embed_dots + DotConfig确定性 PRNG(xorshift64)按网格落点;同一 seed 完全可复现 → 可提取、可溯源
    Logo 水印RgbaImage::embed_logo任意 RGBA 图叠加,支持透明 PNG
    平铺水印RgbaImage::embed_tiled + TiledConfig全幅网格平铺,防截屏/盗摄

    #不可见水印(已实现)

    API说明
    RgbaImage::embed_invisible + extract_invisibleLSB 逐位嵌入(RGB 通道各 1 bit/像素);key 派生 keystream 整体混淆,错误 key 在 magic 校验处被拒绝;容量 = 宽×高×3/8 字节
    verify_invisiblemagic 校验通过即判定存在

    #提取、验证与溯源(已实现)

    API说明
    extract_dots用同一 DotConfig 复现点位置,统计深色命中率(0.0~1.0)
    verify_dots命中率 ≥ 阈值(默认 0.9)判定水印存在
    verify_invisibleLSB 水印 magic 校验
    trace_dots对候选 DotConfig 数组溯源:返回命中率最高者索引(≥ 阈值);分发场景为每个接收者分配独立 seed,泄漏即锁定接收者

    #图像 IO(基于 mizchi/image)

    decode_png / encode_png / decode_jpeg / encode_jpeg / resize / crop

    #配置持久化(已实现)

    text_config_to_json/from_json、dot_config_to_json/from_json、tiled_config_to_json/from_json(core @json,seed 以 Number 表示)

    #典型应用场景

    四种能力可按需组合,覆盖图片保护的常见诉求:

    场景推荐能力机制
    电商主图 / 详情页防同行盗图可见(Logo / 文本)店铺名 + 商品信息角落或平铺,搬运必留痕
    素材 / 图库预览防盗可见 + 不可见预览图盖半透明水印;样图嵌客户 ID
    内部设计稿 / 文档截图泄密溯源trace_dots每个员工 / 供应商独立 seed,外泄即定位
    图片版权确权(摄影 / 插画 / 设计)LSB 不可见创作时嵌入作者 ID + 时间,维权时提取作证
    API 图片 / 头像接口追踪LSB 不可见按调用方嵌入 ID,识别爬虫、盗链、二次分发
    广告 / 营销素材渠道追踪trace_dots不同投放渠道不同 seed,泄漏渠道一测便知
    社交 / UGC 平台防二创搬运平铺全幅网格水印,截图翻拍仍可见
    直播 / 视频切片防搬运可见 + 点阵(动态)平台名 + 时间戳 + 观众 ID,配合 OBS 浏览器源落地
    报告 / 报表 / 合同截图防外泄平铺 + 文本公司名 + 部门,截屏即留痕
    取证存证文本 + LSB时间 + 拍摄者身份,形成证据链

    选型速记:要看得见震慑 → 可见水印;要看不见取证 → LSB;要出事能定位人 → 按接收者分发 seed 走 trace_dots。

    #安装

    moon add yuzhiblue/moon-watermark

    #Quick Start

    完整可运行示例见 cmd/main/main.mbt(moon run cmd/main):

    // 1. 生成测试图(或 decode_png 解码真实图片)
    let img = @lib.RgbaImage::new(320, 240, 0xFFFF_FFFF)

    // 2. 文本水印:平台名 + 时间戳(直播贴片场景)
    let tcfg = @lib.TextConfig::new("LIVE 2026-10-03", 8, 8, 2, 0.6, 0xFF00_00FF)
    let _ = img.embed_text(tcfg)

    // 3. 点阵溯源水印:seed = 观众/接收者 ID
    let dcfg = @lib.DotConfig::default()
    let _ = img.embed_dots(dcfg)

    // 4. 编码 → 解码 → 提取验证
    let png = img.encode_png()
    let back = @lib.decode_png(png.unwrap()).unwrap()
    let rate = @lib.extract_dots(back, dcfg) // ≥0.9 判定水印存在

    #演示

    嵌入前后对比(左上原图 → 右上 LSB 不可见水印 → 左下可见水印 → 右下差异放大 ×64 显示 LSB 实际改动位置):

    嵌入前后对比

    溯源演示(三位接收者持不同 seed,泄漏帧命中接收者 C):

    溯源演示

    鲁棒性矩阵可视化(数据来自 robustness_wbtest.mbt 实测):

    鲁棒性矩阵

    #鲁棒性测试矩阵(实测)

    moon test 的 robustness_wbtest.mbt 实测数据(128×128 测试图,DotConfig::default(),density=0.05):

    攻击点阵水印命中率LSB 不可见水印说明
    PNG 无损往返1.0 ✅可提取 ✅LSB 的可靠载体
    JPEG q60 重压1.0 ✅提取失败 ❌深色点高对比经 JPEG 保留;LSB 被量化破坏 → JPEG 场景需 DCT 域水印(roadmap)
    裁剪半幅(保留行格)1.0 ✅—点阵网格按行列索引相对化,裁剪不破坏行格时完全命中
    缩放至 1/2(Nearest)0.0 ❌—像素网格重采样后点位置错位;缩放鲁棒需频域方案(roadmap)

    #设计说明

    • 颜色打包:0xRRGGBBAA。MoonBit Int 为 32 位有符号,0xFFFF_FFFF 等高位字面量会表示为负数——位运算完全等价,无需担心;推荐用 (r << 16) | (g << 8) | b | (a << 24) 构造颜色。
    • 确定性:点阵水印完全由 DotConfig(含 seed)决定,这是提取与溯源的基础。
    • PNG 无损往返:PNG 编码/解码不损失 LSB 信息,是不可见水印的载体(见路线图)。

    #生态区分

    包作者定位与 moon-watermark 差异
    pixelforge0717lee通用图像处理(合成/滤镜/编解码)无安全水印的嵌入-提取-溯源概念
    moonwatermarkkitOyc996流处理"事件时间水印"非图像水印
    watermark-toolkitV1GreenSummerLLM 文本水印非图像水印

    #路线图

    • W1(10/3–10/9):可见水印全家桶 ✅ · 配置 JSON ✅ · 可运行示例 ✅
    • W2(10/10–10/16):不可见水印 LSB ✅(key 混淆 + magic 校验)· 统一 verify/trace API ✅ · PNG 无损往返集成测试 ✅
    • W3(10/17–10/23):鲁棒性测试矩阵 ✅(实测数据见上表)· CLI 评估 ✅(MoonBit core 暂无进程参数与文件 IO API,WASI 社区包接入会改变 target 配置;cmd/main 内存闭环示例已满足发布「可运行示例」要求,CLI 推迟至 v0.2 native 发布)· 发布 mooncakes
    • 11 月:直播贴片工坊应用(OBS / 直播伴侣浏览器源)复用本库

    #License

    Apache-2.0

    DotConfig

    pub struct DotConfig {
    density : Double
    dot_size : Int
    seed : Int64
    opacity : Double
    grid_offset_x : Int
    grid_offset_y : Int
    }

    随机点阵水印配置。

    • density:点阵密度(0.0 ~ 1.0,越大点越密)。
    • dot_size:单个点的大小(像素边长)。
    • seed:确定性 PRNG 种子;同一种子在不同运行间生成相同水印(可复现、可验证)。
    • opacity:点的不透明度(0.0 ~ 1.0)。
    • grid_offset_x / grid_offset_y:点阵网格偏移,用于多平台差异化。

    DotConfig::default

    fn DotConfig::default() -> DotConfig

    默认配置:密度 0.05、点大小 1、种子 42、全不透明、无偏移。

    DotConfig::new

    fn DotConfig::new(density : Double, dot_size : Int, seed : Int64, opacity : Double, grid_offset_x : Int, grid_offset_y : Int) -> DotConfig

    显式构造:供溯源场景为不同接收者分配不同 seed。

    RgbaImage

    pub struct RgbaImage {
    width : Int
    height : Int
    data : FixedArray[Byte]
    }

    RGBA 图像数据结构:每像素 4 字节(R,G,B,A),行优先存储。

    内部用 FixedArray[Byte] 存储以便像素级原地修改(水印嵌入零复制); 与 mizchi/image 的 ImageData(Bytes)互转见 decode_png / encode_png。

    RgbaImage::crop

    fn RgbaImage::crop(self : RgbaImage, x : Int, y : Int, w : Int, h : Int) -> RgbaImage

    裁剪图像区域 (x, y, w, h)。越界坐标被钳制到图像范围内。

    RgbaImage::embed_dots

    fn RgbaImage::embed_dots(self : RgbaImage, cfg : DotConfig) -> RgbaImage

    随机点阵水印:按 DotConfig 的网格 + PRNG 布局,在全图散布深色点。

    • 图像按 8×8 单元划分网格;每个单元由 seed 驱动的确定性 PRNG 决定是否落点(density 控制落点单元比例)及点在单元内的偏移 (叠加 grid_offset_x / grid_offset_y)。
    • 点颜色固定为深色(0x0000_00FF),半透明时由 opacity 控制; 同一配置(尤其同一 seed)嵌入结果完全一致,可被 extract_dots 复现。

    RgbaImage::embed_invisible

    fn RgbaImage::embed_invisible(self : RgbaImage, payload : Bytes, key? : Int64) -> Result[RgbaImage, String]

    将 payload(如观众 ID)以 LSB 方式嵌入图像(原地修改)。

    • 容量:width × height × 3 / 8 字节(含 4 字节头部);超出返回 Err。
    • key:可选混淆密钥,None 为明文嵌入。
    • 返回 Ok(图像) / Err(容量不足)。
    fn RgbaImage::embed_logo(self : RgbaImage, logo : RgbaImage, x : Int, y : Int, opacity : Double) -> RgbaImage

    Logo 水印:将 logo 图像叠加到 (x, y) 处(原地修改并返回同一图像)。

    • logo 的自身 alpha 参与混合(透明背景的 PNG 可直接使用)。
    • opacity(0.0~1.0)控制整体透明度;越界部分自动裁剪。

    RgbaImage::embed_text

    fn RgbaImage::embed_text(self : RgbaImage, cfg : TextConfig) -> RgbaImage

    在图像指定位置叠加文本水印(原地修改并返回同一图像)。

    位置由 TextConfig.x / y 指定(水印左上角);透明度仅由 opacity 控制(字形像素不透明);越界部分自动裁剪。

    RgbaImage::embed_tiled

    fn RgbaImage::embed_tiled(self : RgbaImage, cfg : TiledConfig) -> RgbaImage

    平铺水印:将 TiledConfig.text 渲染出的水印单元按间距平铺覆盖整张图 (原地修改并返回同一图像)。常用于防盗摄的全幅斜向/网格水印。

    RgbaImage::encode_jpeg

    fn RgbaImage::encode_jpeg(self : RgbaImage, quality : Int) -> Result[Bytes, String]

    将 RgbaImage 编码为 JPEG 字节(质量 0~100,用于鲁棒性测试)。

    RgbaImage::encode_png

    fn RgbaImage::encode_png(self : RgbaImage) -> Result[Bytes, String]

    将 RgbaImage 编码为 PNG 字节。

    RgbaImage::get_pixel

    fn RgbaImage::get_pixel(self : RgbaImage, x : Int, y : Int) -> Int

    读取像素,返回打包 RGBA 颜色 0xRRGGBBAA。

    RgbaImage::new

    fn RgbaImage::new(width : Int, height : Int, rgba : Int) -> RgbaImage

    创建指定尺寸的纯色图像。

    参数 rgba 为打包颜色:0xRRGGBBAA。

    RgbaImage::resize

    fn RgbaImage::resize(self : RgbaImage, width : Int, height : Int, sampler : SampleMode) -> RgbaImage

    缩放图像。非法尺寸时返回原图副本。

    RgbaImage::set_pixel

    fn RgbaImage::set_pixel(self : RgbaImage, x : Int, y : Int, rgba : Int) -> Unit

    写入像素,rgba 为打包颜色 0xRRGGBBAA。

    SampleMode

    pub(all) enum SampleMode {
    Nearest
    Bilinear
    }

    缩放采样方式。

    TextConfig

    pub struct TextConfig {
    text : String
    x : Int
    y : Int
    font_size : Int
    opacity : Double
    angle : Double
    color : Int
    }

    文本水印配置。

    • x / y:水印左上角在目标图像中的坐标(像素)。
    • font_size:点阵字体的缩放倍数(1 = 原始 5×7 尺寸)。
    • opacity:整体不透明度(0.0 ~ 1.0)。
    • angle:旋转角度(度)。当前版本保留字段,暂未实现旋转。
    • color:打包 RGBA 颜色 0xRRGGBBAA。

    TextConfig::new

    fn TextConfig::new(text : String, x : Int, y : Int, font_size : Int, opacity : Double, color : Int) -> TextConfig

    便捷构造器:angle 默认为 0(当前版本未实现旋转)。

    TiledConfig

    pub struct TiledConfig {
    text : String
    font_size : Int
    spacing_x : Int
    spacing_y : Int
    opacity : Double
    color : Int
    }

    平铺水印配置:text 按 font_size 放大为单元,以 spacing_x / spacing_y 为间距平铺覆盖整张图。

    TiledConfig::new

    fn TiledConfig::new(text : String, font_size : Int, spacing_x : Int, spacing_y : Int, opacity : Double, color : Int) -> TiledConfig

    便捷构造器(颜色打包 RGBA 0xRRGGBBAA)。

    blend_pixel

    fn blend_pixel(dst : Int, src : Int, alpha : Int) -> Int

    标准 alpha 混合:将 src 以不透明度 alpha 叠加到 dst 上。

    逐通道计算:out = (src * a + dst * (255 - a)) / 255, 输入输出均为打包 RGBA 颜色 0xRRGGBBAA,alpha 取值 0~255。

    decode_jpeg

    fn decode_jpeg(raw : Bytes) -> Result[RgbaImage, String]

    从 JPEG 字节解码为 RgbaImage。

    decode_png

    fn decode_png(raw : Bytes) -> Result[RgbaImage, String]

    从 PNG 字节解码为 RgbaImage。

    dot_config_from_json

    fn dot_config_from_json(s : String) -> DotConfig?

    从 JSON 字符串解析 DotConfig;字段缺失或格式错误返回 None。

    dot_config_to_json

    fn dot_config_to_json(cfg : DotConfig) -> String

    将 DotConfig 序列化为 JSON 字符串。

    extract_dots

    fn extract_dots(img : RgbaImage, cfg : DotConfig, brightness_threshold? : Int) -> Double

    提取点阵水印:用同一 cfg 复现点位置,统计深色命中率(0.0~1.0)。

    命中判定:像素亮度(RGB 均值)低于 brightness_threshold 视为命中。 用于验证水印是否存在 / 匹配某个 seed(溯源时不同接收者持有不同 seed)。

    extract_invisible

    fn extract_invisible(img : RgbaImage, key? : Int64) -> Bytes?

    从图像提取 LSB 不可见水印;magic 校验失败(含错误 key)返回 None。

    render_text

    fn render_text(text : String, color : Int, scale? : Int) -> RgbaImage

    将文本渲染为 RGBA 位图(内置 5×7 点阵字体)。

    • color:字形颜色,打包 RGBA 0xRRGGBBAA;字形像素不透明时 alpha 取 255。
    • scale(可选,默认 1):点阵放大倍数,用最近邻放大。
    • 字符间距 1 像素;不支持的字形渲染为空白。
    • 空文本返回 0×0 图像。

    seed_to_state

    fn seed_to_state(seed : Int64) -> UInt64

    将 seed(Int64)转换为 PRNG 初始状态。

    text_config_from_json

    fn text_config_from_json(s : String) -> TextConfig?

    从 JSON 字符串解析 TextConfig;字段缺失或格式错误返回 None。

    text_config_to_json

    fn text_config_to_json(cfg : TextConfig) -> String

    将 TextConfig 序列化为 JSON 字符串。

    tiled_config_from_json

    fn tiled_config_from_json(s : String) -> TiledConfig?

    从 JSON 字符串解析 TiledConfig;字段缺失或格式错误返回 None。

    tiled_config_to_json

    fn tiled_config_to_json(cfg : TiledConfig) -> String

    将 TiledConfig 序列化为 JSON 字符串。

    trace_dots

    fn trace_dots(img : RgbaImage, candidates : Array[DotConfig], threshold? : Double) -> Int

    溯源:对候选 DotConfig 逐一提取命中率,返回最高命中率候选的索引。

    候选为空或最高命中率低于 threshold(默认 0.9)时返回 -1。 直播溯源场景:每个接收者持有不同 seed 的配置,泄漏视频命中哪个 seed 即锁定对应接收者。

    verify_dots

    fn verify_dots(img : RgbaImage, cfg : DotConfig, threshold? : Double) -> Bool

    验证点阵水印是否存在:extract_dots 命中率 ≥ threshold(默认 0.9)。

    verify_invisible

    fn verify_invisible(img : RgbaImage, key? : Int64) -> Bool

    验证 LSB 不可见水印是否存在:能通过 magic 校验(含正确 key)即存在。

    xorshift64_next

    fn xorshift64_next(state : UInt64) -> UInt64

    确定性 64 位 PRNG(xorshift64)。

    相同种子产生完全相同序列,保证点阵水印可复现、可提取; 种子 0 会被替换为 1,避免全零状态死循环。