Fix DCT steganography for non-8-aligned images and set color mode default

- Fix block calculation mismatch in DCT extract (use original dimensions)
- Change default dct_color_mode from "grayscale" to "color"
- Update DCT test to use noise image instead of solid color
- Remove debug logging from encode/decode paths

The block calculation fix ensures extract uses the same block positions
as embed for images whose dimensions aren't divisible by 8. This was
causing decode failures on the Pi web UI with 1195x671 images.

Color mode is now the default since it preserves the original image
colors. The test fixture now uses a random noise image because solid
color images cause coefficient drift during YCbCr/RGB conversion that
can corrupt embedded data.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee
2026-01-04 21:36:59 -05:00
parent 7a5092b945
commit aac8037c04
7 changed files with 23 additions and 61 deletions

View File

@@ -886,16 +886,6 @@ def encode_page():
ref_data = ref_photo.read()
carrier_data = carrier.read()
# DEBUG: Log file hashes to verify bytes match between encode/decode
import hashlib
import sys
ref_hash = hashlib.md5(ref_data).hexdigest()[:8]
carrier_hash = hashlib.md5(carrier_data).hexdigest()[:8]
print(f"[ENCODE DEBUG] ref: {len(ref_data)} bytes, md5: {ref_hash}", file=sys.stderr)
print(f"[ENCODE DEBUG] carrier: {len(carrier_data)} bytes, md5: {carrier_hash}", file=sys.stderr)
print(f"[ENCODE DEBUG] passphrase: '{passphrase}', pin: '{pin}'", file=sys.stderr)
print(f"[ENCODE DEBUG] channel_key: '{channel_key}', embed_mode: '{embed_mode}'", file=sys.stderr)
# Handle RSA key - can come from .pem file or QR code image
rsa_key_data = None
rsa_key_qr = request.files.get("rsa_key_qr")
@@ -1019,8 +1009,6 @@ def encode_page():
# Store temporarily
file_id = secrets.token_urlsafe(16)
cleanup_temp_files()
stego_hash = hashlib.md5(encode_result.stego_data).hexdigest()[:8]
print(f"[ENCODE RESULT] stego: {len(encode_result.stego_data)} bytes, md5: {stego_hash}", file=sys.stderr)
TEMP_FILES[file_id] = {
"data": encode_result.stego_data,
"filename": filename,
@@ -1102,10 +1090,6 @@ def encode_download(file_id):
file_info = TEMP_FILES[file_id]
mime_type = file_info.get("mime_type", "image/png")
import hashlib
download_hash = hashlib.md5(file_info["data"]).hexdigest()[:8]
print(f"[DOWNLOAD] stego: {len(file_info['data'])} bytes, md5: {download_hash}", file=sys.stderr)
return send_file(
io.BytesIO(file_info["data"]),
mimetype=mime_type,
@@ -1193,15 +1177,6 @@ def decode_page():
ref_data = ref_photo.read()
stego_data = stego_image.read()
# DEBUG: Log file hashes to verify bytes match between encode/decode
import hashlib
ref_hash = hashlib.md5(ref_data).hexdigest()[:8]
stego_hash = hashlib.md5(stego_data).hexdigest()[:8]
print(f"[DECODE DEBUG] ref: {len(ref_data)} bytes, md5: {ref_hash}", file=sys.stderr)
print(f"[DECODE DEBUG] channel_key: '{channel_key}'", file=sys.stderr)
print(f"[DECODE DEBUG] stego: {len(stego_data)} bytes, md5: {stego_hash}", file=sys.stderr)
print(f"[DECODE DEBUG] passphrase: '{passphrase}', pin: '{pin}', embed_mode: '{embed_mode}'", file=sys.stderr)
# Handle RSA key - can come from .pem file or QR code image
rsa_key_data = None
rsa_key_qr = request.files.get("rsa_key_qr")
@@ -1256,7 +1231,6 @@ def decode_page():
)
# Check for subprocess errors
print(f"[DECODE RESULT] success={decode_result.success}, error={decode_result.error}", file=sys.stderr)
if not decode_result.success:
error_msg = decode_result.error or "Decoding failed"
# Check for channel key related errors

View File

@@ -98,16 +98,6 @@ def encode_operation(params: dict) -> dict:
# Resolve channel key (v4.0.0)
resolved_channel_key = _resolve_channel_key(params.get("channel_key", "auto"))
# DEBUG: Log what channel key the worker is using
import sys, os
from stegasoo.channel import get_channel_key, get_channel_key_hash
worker_channel_key = get_channel_key()
worker_channel_hash = get_channel_key_hash()
print(f"[WORKER ENCODE] cwd={os.getcwd()}", file=sys.stderr)
print(f"[WORKER ENCODE] channel_key param='{params.get('channel_key')}' -> resolved='{resolved_channel_key}'", file=sys.stderr)
print(f"[WORKER ENCODE] get_channel_key()={worker_channel_key}", file=sys.stderr)
print(f"[WORKER ENCODE] get_channel_key_hash()={worker_channel_hash[:8].hex() if worker_channel_hash else None}", file=sys.stderr)
# Call encode with correct parameter names
result = encode(
message=payload,
@@ -161,16 +151,6 @@ def decode_operation(params: dict) -> dict:
# Resolve channel key (v4.0.0)
resolved_channel_key = _resolve_channel_key(params.get("channel_key", "auto"))
# DEBUG: Log what channel key the worker is using
import sys, os
from stegasoo.channel import get_channel_key, get_channel_key_hash
worker_channel_key = get_channel_key()
worker_channel_hash = get_channel_key_hash()
print(f"[WORKER DECODE] cwd={os.getcwd()}", file=sys.stderr)
print(f"[WORKER DECODE] channel_key param='{params.get('channel_key')}' -> resolved='{resolved_channel_key}'", file=sys.stderr)
print(f"[WORKER DECODE] get_channel_key()={worker_channel_key}", file=sys.stderr)
print(f"[WORKER DECODE] get_channel_key_hash()={worker_channel_hash[:8].hex() if worker_channel_hash else None}", file=sys.stderr)
# Call decode with correct parameter names
result = decode(
stego_image=stego_data,

View File

@@ -179,10 +179,6 @@ class SubprocessStego:
cwd=str(self.worker_path.parent),
)
# DEBUG: Log worker stderr to main process stderr
if result.stderr:
print(result.stderr, file=sys.stderr, end='')
if result.returncode != 0:
# Worker crashed
return {