binary_data = "" # 1. 遍历像素并提取LSB for y inrange(height): for x inrange(width): pixel = img_array[y, x] for i inrange(3): binary_data += str(pixel[i] & 1)
@logger.catch def_message_to_binary(message: str) -> str: """Converts a string message into its binary representation.""" binary_message = ''.join(format(ord(char), '08b') for char in message) return binary_message
@logger.catch def_binary_to_message(binary_message: str) -> str: """Converts a binary string back into a string message.""" # Ensure the binary string is a multiple of 8 iflen(binary_message) % 8 != 0: logger.warning("Binary string length is not a multiple of 8; data may be incomplete.") binary_message = binary_message[:-(len(binary_message) % 8)]
@logger.catch defencode_lsb(input_image_path: str, secret_message: str, output_image_path: str): """ Encodes a secret message into an image using the LSB (Least Significant Bit) technique. The output image will be saved in a lossless format (PNG) to preserve the watermark. Args: input_image_path (str): The path to the source image. secret_message (str): The message to hide. output_image_path (str): The path to save the watermarked image. """ try: logger.info(f"Starting LSB encoding for '{input_image_path}'...") image = Image.open(input_image_path, 'r').convert("RGBA") # Convert to RGBA for consistency width, height = image.size img_array = np.array(list(image.getdata()))
channels = 4# We are using RGBA
# Add a unique delimiter to know where the message ends binary_secret_message = _message_to_binary(secret_message + "####") required_pixels = len(binary_secret_message)
if required_pixels > width * height * channels: logger.error("Error: Message is too long to be encoded in this image.") return
# Modify the LSB of the pixel data data_index = 0 flat_array = img_array.flatten()
for i inrange(len(flat_array)): if data_index < required_pixels: # Change the LSB of the color value flat_array[i] = int(bin(flat_array[i])[2:-1] + binary_secret_message[data_index], 2) data_index += 1 else: break# Stop once the message is encoded
# Create a new image with the modified pixel data encoded_image = Image.fromarray(flat_array.reshape(height, width, channels).astype('uint8'), image.mode)
# Save as PNG to ensure lossless storage of the watermark encoded_image.save(output_image_path, "PNG") logger.info(f"Successfully encoded message into '{output_image_path}'.")
except FileNotFoundError: logger.error(f"Error: Input image not found at '{input_image_path}'.") except Exception as e: logger.exception(f"An unexpected error occurred during encoding: {e}")
@logger.catch defdecode_lsb(encoded_image_path: str) -> str | None: """ Decodes a secret message from an image using the LSB technique. Args: encoded_image_path (str): The path to the watermarked image. Returns: The decoded secret message, or None if an error occurs or no message is found. """ try: logger.info(f"Starting LSB decoding for '{encoded_image_path}'...") image = Image.open(encoded_image_path, 'r') img_array = np.array(list(image.getdata()))
binary_data = "" for pixel in img_array: for value in pixel: binary_data += bin(value)[-1]
# Find the delimiter by decoding byte by byte decoded_message = "" for i inrange(0, len(binary_data), 8): byte = binary_data[i:i+8] iflen(byte) < 8: break# End of data decoded_message += chr(int(byte, 2)) if decoded_message.endswith("####"): logger.info("Successfully decoded the message.") return decoded_message[:-4] # Return message without the delimiter
logger.warning("Could not find the end-of-message delimiter in the image.") returnNone
except FileNotFoundError: logger.error(f"Error: Encoded image not found at '{encoded_image_path}'.") returnNone except Exception as e: logger.exception(f"An unexpected error occurred during decoding: {e}") returnNone
@logger.catch defvalid_img_msg(image_path): """ Command-line tool to decode an invisible watermark from an image file. """ image_path = Path(image_path)
ifnot image_path.is_file(): logger.debug(f"Error: File not found at '{image_path}'") return
if decoded_message: logger.debug("\n--- ✅ Watermark Found! ---") # Pretty logger.debug the decoded information for item in decoded_message.split('|'): if':'in item: key, value = item.split(':', 1) logger.debug(f" - {key.strip():<15}: {value.strip()}") else: logger.debug(f" - {item}") logger.debug("--------------------------\n") else: logger.debug("\n--- ❌ No Watermark Found ---") logger.debug("No hidden message could be decoded from this image.") logger.debug("--------------------------\n") return decoded_message