Ownership needs proof
Reusable controls keep releases consistent.
4 studio workflowsUpload logo (PNG recommended).
Upload or drop an image to start
The problem we're solving
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.
Reusable controls keep releases consistent.
4 studio workflowsLSB controls balance payload capacity and image quality.
Reveal checks try every supported LSB depth automatically.
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.
- 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.
10110001010110000 (LSB replaced)
- 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.
- 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.
- 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.
os, sys, math, base64, io, json, argparse.
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:
10110001010110000
If using a higher Strength (e.g., 3 LSBs), we replace the last 3 bits. To hide 101 (which is 5 in decimal):
1011000110110110101The 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