| Crates.io | blind_watermark |
| lib.rs | blind_watermark |
| version | 0.1.2 |
| created_at | 2025-12-04 01:15:34.866249+00 |
| updated_at | 2025-12-05 08:24:52.901915+00 |
| description | Picture blind watermarking. |
| homepage | https://github.com/naganohara-yoshino/blind-watermark-rust |
| repository | https://github.com/naganohara-yoshino/blind-watermark-rust |
| max_upload_size | |
| id | 1965622 |
| size | 6,003,914 |
A Rust library and CLI tool for blind image watermarking using DWT (Discrete Wavelet Transform), DCT (Discrete Cosine Transform), and SVD (Singular Value Decomposition). The algorithm is originally implemented in python by Guo Fei at this repository. This is the rust rewritten and modified version.
faer crate for efficient matrix computations and rayon for multi-threading support.Vec<u8>). String watermarking is natively supported.

Pre-Compiled Binaries are available at GitHub Releases. If you want to install from source, use:
cargo install blind_watermark
You can get help by running:
blind_watermark --help
Add this to your Cargo.toml:
[dependencies]
blind_watermark = "0.1"
use blind_watermark::prelude::*;
fn main() {
let example = "example.jpg";
let processed = "processed.png";
let watermark = "こんにちは❗😊";
let seed = Some(0);
embed_watermark_string(example, processed, watermark, seed).unwrap();
}
OR
use bitvec::prelude::*;
use image::{DynamicImage, ImageReader, Rgba32FImage};
fn main() {
// 1. Load the image using the `image` crate
let img = ImageReader::open("input.jpg")
.expect("Failed to open image")
.decode()
.expect("Failed to decode image")
.into_rgba32f();
// 2. Convert to YCbCrA format
let ycbcr: YCrBrAMat = img.into();
// 3. Define watermark (bits)
let watermark = bits![u8, Lsb0; 0, 1, 0, 1, 1, 0, 1, 0]; // Example bits
// 4. Configure watermark settings (Optional)
let config = WatermarkConfigBuilder::default()
.strength_1(36)
.mode(WatermarkMode::Strategy(12345)) // Use random strategy with seed
.build()
.unwrap();
// 5. Process pipeline: Padding -> DWT -> Cut Blocks -> Embed -> Assemble -> IDWT -> Remove Padding
let processed = ycbcr
.add_padding()
.dwt()
.cut()
.embed_watermark_bits(watermark, &config)
.assemble()
.idwt()
.remove_padding();
// 6. Save the result
let processed_image: Rgba32FImage = processed.into();
let output_image: DynamicImage = processed_image.into();
output_image.to_rgb8().save("watermarked.png").unwrap();
}
To extract the watermark, you only need the watermarked image and the length of the watermark.
use blind_watermark::prelude::*;
fn main() {
let processed = "processed.png";
let watermark_len = get_wm_len("こんにちは❗😊".as_bytes());
let seed = Some(0);
let extracted = extract_watermark_string(processed, watermark_len, seed).unwrap();
println!("Extracted bits: {:?}", extracted);
}
OR
use bitvec::prelude::*;
use blind_watermark::prelude::*;
use image::ImageReader;
fn main() {
// 1. Load the watermarked image
let img = ImageReader::open("watermarked.png")
.expect("Failed to open image")
.decode()
.expect("Failed to decode image")
.into_rgba32f();
let ycbcr: YCrBrAMat = img.into();
// 2. Use the same config as embedding
let config = WatermarkConfigBuilder::default()
.strength_1(36)
.mode(WatermarkMode::Strategy(12345))
.build()
.unwrap();
let watermark_len = 8; // Length of the embedded watermark
// 3. Process pipeline: Padding -> DWT -> Cut Blocks -> Extract
let extracted_bits = ycbcr
.add_padding()
.dwt()
.cut()
.extract_watermark_bits(watermark_len, &config);
println!("Extracted bits: {:?}", extracted_bits);
The library implements a hybrid DWT-DCT-SVD watermarking scheme:
This approach ensures that the watermark is embedded in the significant features of the image, providing robustness while maintaining visual quality.
Licensed under either of
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.