Add Watermark
Reveal
Remove

Settings

Layout

Preview & Download

Upload or drop an image to start

The problem we're solving

Images travel fast. Trust does not.

StegaMark gives image owners a focused workspace for visible brand marks, hidden payloads, fast reveal checks, and cleanup without turning the workflow into a forensic maze.

Ownership needs proof

Reusable controls keep releases consistent.

4 studio workflows

Hidden data needs precision

LSB controls balance payload capacity and image quality.

Verification needs speed

Reveal checks try every supported LSB depth automatically.

Processing Overview

StegaMark runs the hosted studio in the browser. The original Python and Flask workflow remains useful for local CLI/server use, while GitHub Pages serves the static interface.

Key Functionalities:

1. Invisible Watermarking (LSB Steganography)

- Hides data within the least significant bits (LSBs) of image pixels. Think of each pixel having color channels (Red, Green, Blue, sometimes Alpha), each represented by a number (usually 0-255). This number can be seen as 8 bits (e.g., 255 is 11111111, 177 is 10110001).
- The LSB is the rightmost bit (10110001). Changing this bit causes only a tiny change in the color value (e.g., changing 10110001 (177) to 10110000 (176)), which is usually imperceptible to the human eye.
- encode_image: Converts secret data (text/image) into a stream of 0s and 1s. It then iterates through the image pixels, replacing the LSB of selected color channels with the next bit from the secret stream. A unique delimiter (DELIMITER_BIN) is appended to mark the end.
- decode_image: Reads the LSBs from the image pixels in the same order, reconstructs the binary stream, and stops when it finds the delimiter, returning the data found before it.
- Strength: Controls how many LSBs (1 to 8) per channel are used. Higher strength embeds more data faster and is more robust to *some* compression, but makes larger changes to the pixel values, increasing the risk of visual artifacts. Requires lossless PNG format for saving, as JPEG compression destroys LSB data.

LSB Example (1 bit): Original Pixel Value (Red): 177 = 10110001
Secret Bit to Hide: 0
New Pixel Value (Red): 176 = 10110000 (LSB replaced)

2. Visible Watermarking

- Handled in the hosted studio with the browser Canvas API, with the original add_visible_watermark function available in the Python workflow.
- Supports both text and logo watermarks.
- Creates a canvas the size of the base image and draws the visible mark directly onto it.
- Renders text with browser fonts or resizes the uploaded logo before compositing it into the image.
- Applies opacity to the watermark element.
- Calculates position (_calculate_position) for single placement or uses tiling logic for repeated patterns ('grid', 'staggered', 'diagonal').
- For tiling, it calculates step sizes based on watermark dimensions and spacing, potentially rotates the element ('diagonal' style), and pastes it repeatedly onto the overlay.
- Finally, exports the processed image as a downloadable browser-generated file.

3. Hosted Web Interface

- GitHub Pages serves index.html, style.css, main.js, and local image assets as static files.
- Visible watermarking, LSB embedding, reveal checks, and cleanup all run in the user's browser, so no hosted Flask route is required for the public page.
- A Flask app can still be used locally or on a separate backend host for CLI/API workflows, but it is not part of the GitHub Pages deployment.
- Includes browser-side error handling for file issues, data size limits, and processing errors.

4. Command Line Interface (CLI)

- Uses Python's argparse module to provide command-line access to the core functionalities.
- Subcommands (encode-visible, encode-invisible, decode, web) allow running operations directly from the terminal without the web UI.
- Provides an alternative way to integrate StegaMark into scripts or batch processes.

Core Libraries:

  • Browser Canvas API: For the hosted studio's image loading, drawing, compositing, and export operations.
  • Pillow: For the optional Python CLI/local server workflow.
  • Flask: For optional local or separately hosted HTTP API workflows.
  • Standard Libraries: os, sys, math, base64, io, json, argparse.

Conceptual Example: How LSB Works

Imagine a single pixel's Red color value is 177. In binary (8 bits), this is 10110001. The Least Significant Bit (LSB) is the rightmost bit (1). If we want to hide the secret bit 0, we simply replace the LSB:

Example (1 LSB): Original Red Value: 177 = 10110001
Secret Bit to Hide: 0
New Red Value: 176 = 10110000
Notice the tiny change (177 -> 176), visually negligible.

If using a higher Strength (e.g., 3 LSBs), we replace the last 3 bits. To hide 101 (which is 5 in decimal):

Example (3 LSBs): Original Red Value: 177 = 10110001
Secret Bits to Hide: 101
New Red Value: 181 = 10110101
Change is larger (177 -> 181), potentially more noticeable.

The Python code below demonstrates the bitwise operations used to achieve this:


# Conceptual LSB Encoding Snippet (Simplified)
# Example: Hiding the first letter 'S' from "StegaMark" using 1 LSB per color channel.

# 1. Convert the character 'S' to its binary representation.
#    - ASCII value of 'S' is 83.
#    - 8-bit binary for 83 is 01010011.
#    - These 8 bits need to be hidden sequentially.

# 2. Take the first pixel's Red channel value (or the next available channel).
pixel_value_1 = 177 # Example: Original Red value (Binary: 10110001)

# 3. Take the first bit of 'S' to hide.
first_secret_bit = '0' # First bit from '01010011'

# 4. Modify the pixel value to hide this bit in its LSB.
#    Goal: Change LSB of 10110001 to match '0'. Expected: 10110000 (176)

#    a. Create mask to clear the LSB: 254 (11111110)
clear_mask_1 = 254
#    b. Apply mask using bitwise AND (&):
#       10110001 (177) & 11111110 (254) = 10110000 (176)
cleared_value_1 = pixel_value_1 & clear_mask_1
#    c. Convert secret bit '0' to integer 0.
secret_bit_value_1 = int(first_secret_bit, 2)
#    d. Insert secret bit using bitwise OR (|):
#       10110000 (176) | 00000000 (0) = 10110000 (176)
new_pixel_value_1 = cleared_value_1 | secret_bit_value_1

# 5. Move to the next pixel's color channel (e.g., Green channel of the same pixel, or Red of the next pixel).
pixel_value_2 = 215 # Example: Original Green value (Binary: 11010111)

# 6. Take the second bit of 'S' to hide.
second_secret_bit = '1' # Second bit from '01010011'

# 7. Modify the second pixel value to hide this bit in its LSB.
#    Goal: Change LSB of 11010111 to match '1'. Expected: 11010111 (215 - no change needed!)

#    a. Clear LSB: 215 & 254 = 214 (11010110)
cleared_value_2 = pixel_value_2 & clear_mask_1
#    b. Convert secret bit '1' to integer 1.
secret_bit_value_2 = int(second_secret_bit, 2)
#    c. Insert secret bit:
#       11010110 (214) | 00000001 (1) = 11010111 (215)
new_pixel_value_2 = cleared_value_2 | secret_bit_value_2

# 8. Repeat this process for all 8 bits of 'S' (01010011), using 8 consecutive
#    pixel color values. Then continue for the next character 't', and so on,
#    until the entire message "StegaMark" and the delimiter are hidden.

# --- Example using 3 LSBs (Conceptual) ---
# If strength=3 was used, we'd hide 3 bits at a time.
# To hide the first 3 bits of 'S' ('010'):
bits_to_hide = '010' # First 3 bits of 'S'
strength = 3
pixel_value_3lsb = 177 # Original value (10110001)

# Goal: Change last 3 bits of 10110001 to match '010'. Expected: 10110010 (178)

# a. Create mask to clear last 3 bits: 248 (11111000)
clear_mask_3 = 0xFF << strength & 0xFF
# b. Clear the bits: 177 & 248 = 176 (10110000)
cleared_value_3lsb = pixel_value_3lsb & clear_mask_3
# c. Convert secret bits '010' to integer 2.
secret_bits_value_3lsb = int(bits_to_hide, 2)
# d. Insert secret bits: 176 | 2 = 178 (10110010)
new_value_3lsb = cleared_value_3lsb | secret_bits_value_3lsb

# --- Output ---
print(f"--- Hiding 'S' (01010011) bit by bit (1 LSB) ---")
print(f"Pixel 1 Original: {pixel_value_1} ({pixel_value_1:08b}), Secret Bit: '{first_secret_bit}', New Value: {new_pixel_value_1} ({new_pixel_value_1:08b})")
print(f"Pixel 2 Original: {pixel_value_2} ({pixel_value_2:08b}), Secret Bit: '{second_secret_bit}', New Value: {new_pixel_value_2} ({new_pixel_value_2:08b})")
print(f"... process continues for remaining 6 bits of 'S' and rest of message ...")
print(f"\n--- Hiding first 3 bits '010' of 'S' (3 LSBs) ---")
print(f"Pixel Original: {pixel_value_3lsb} ({pixel_value_3lsb:08b}), Secret Bits: '{bits_to_hide}', New Value: {new_value_3lsb} ({new_value_3lsb:08b})")

Example CLI Usage:
python app.py encode-invisible --input input.png --message "Secret" --output encoded.png --strength 3
python app.py decode --input encoded.png --strength 3
python app.py encode-visible --input input.jpg --text "© Me" --output visible.jpg --position bottom-right
python app.py web

Team

Built by the StegaMark team.